From 8bd23dd45787d41dc5c12d241acc2912faae77a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Jun 2021 18:59:06 +1200 Subject: [PATCH 001/285] Bump version to v1.19.0b1 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index b50b60a204..159a74adc2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,8 +1,8 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 20 -PATCH_VERSION = "0-dev" +MINOR_VERSION = 19 +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 139db58a6652c670d3222ac04f8260c173316710 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 9 Jun 2021 17:01:05 +0200 Subject: [PATCH 002/285] Simplify LightCall validation (#1874) --- esphome/components/light/light_state.cpp | 43 +++++++----------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 4e450f6ccb..749dea5419 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -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(1.0f); this->blue_ = optional(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(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(1.0f); - } else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) { + } else { this->white_ = optional(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(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - } - // if setting Kelvin from color (i.e. switching to white light), set White to 100% + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(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(1.0f); - } - - if (was_color && !this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - } else { - if (!this->white_.has_value() && was_color && now_white) { - this->white_ = optional(1.0f); - } + if (traits.get_supports_color_interlock() || was_color) { + this->white_ = optional(1.0f); } } From 0db4815f3de8a8b9dac480ad679798102b447bd6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Jun 2021 14:04:39 +1200 Subject: [PATCH 003/285] BLE loop use (#1882) --- esphome/components/esp32_ble/ble.cpp | 78 +++++++---------- esphome/components/esp32_ble/ble.h | 10 +-- .../esp32_ble/ble_characteristic.cpp | 48 +++++++--- .../components/esp32_ble/ble_characteristic.h | 16 +++- .../components/esp32_ble/ble_descriptor.cpp | 15 ++-- esphome/components/esp32_ble/ble_descriptor.h | 13 ++- esphome/components/esp32_ble/ble_server.cpp | 87 +++++++++++-------- esphome/components/esp32_ble/ble_server.h | 18 +++- esphome/components/esp32_ble/ble_service.cpp | 61 ++++++++----- esphome/components/esp32_ble/ble_service.h | 30 +++++-- esphome/components/esp32_ble/queue.h | 29 +++++-- .../esp32_improv/esp32_improv_component.cpp | 25 ++++-- .../esp32_improv/esp32_improv_component.h | 5 +- esphome/components/wifi/wifi_component.cpp | 2 +- 14 files changed, 272 insertions(+), 165 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 2200d3cef1..889c793017 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -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, diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 8850f10d85..27cb9a2092 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -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 ble_events_; diff --git a/esphome/components/esp32_ble/ble_characteristic.cpp b/esphome/components/esp32_ble/ble_characteristic.cpp index 240b57eb38..127f973146 100644 --- a/esphome/components/esp32_ble/ble_characteristic.cpp +++ b/esphome/components/esp32_ble/ble_characteristic.cpp @@ -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; } diff --git a/esphome/components/esp32_ble/ble_characteristic.h b/esphome/components/esp32_ble/ble_characteristic.h index 076f5c1a5f..648cfb939d 100644 --- a/esphome/components/esp32_ble/ble_characteristic.h +++ b/esphome/components/esp32_ble/ble_characteristic.h @@ -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 &)> &func) { this->on_write_ = func; } + void on_write(const std::function &)> &&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 value_; SemaphoreHandle_t set_value_lock_; - SemaphoreHandle_t create_lock_; std::vector descriptors_; std::function &)> 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 diff --git a/esphome/components/esp32_ble/ble_descriptor.cpp b/esphome/components/esp32_ble/ble_descriptor.cpp index 4c4e3968e8..9738cc2fe7 100644 --- a/esphome/components/esp32_ble/ble_descriptor.cpp +++ b/esphome/components/esp32_ble/ble_descriptor.cpp @@ -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; } diff --git a/esphome/components/esp32_ble/ble_descriptor.h b/esphome/components/esp32_ble/ble_descriptor.h index ffbb9645bd..bc7b48e331 100644 --- a/esphome/components/esp32_ble/ble_descriptor.h +++ b/esphome/components/esp32_ble/ble_descriptor.h @@ -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 diff --git a/esphome/components/esp32_ble/ble_server.cpp b/esphome/components/esp32_ble/ble_server.cpp index a63312dd62..9d5be46113 100644 --- a/esphome/components/esp32_ble/ble_server.cpp +++ b/esphome/components/esp32_ble/ble_server.cpp @@ -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: diff --git a/esphome/components/esp32_ble/ble_server.h b/esphome/components/esp32_ble/ble_server.h index 0713816ad3..5657773446 100644 --- a/esphome/components/esp32_ble/ble_server.h +++ b/esphome/components/esp32_ble/ble_server.h @@ -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(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 model_; esp_gatt_if_t gatts_if_{0}; + bool registered_{false}; BLEAdvertising *advertising_; uint32_t connected_clients_{0}; std::map clients_; std::vector services_; - BLEService *device_information_service; + BLEService *device_information_service_; std::vector 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; diff --git a/esphome/components/esp32_ble/ble_service.cpp b/esphome/components/esp32_ble/ble_service.cpp index af8fe00a1d..4b85182696 100644 --- a/esphome/components/esp32_ble/ble_service.cpp +++ b/esphome/components/esp32_ble/ble_service.cpp @@ -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; } diff --git a/esphome/components/esp32_ble/ble_service.h b/esphome/components/esp32_ble/ble_service.h index 9a88d4e1e7..a539631846 100644 --- a/esphome/components/esp32_ble/ble_service.h +++ b/esphome/components/esp32_ble/ble_service.h @@ -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 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 diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index f5c861a917..3c87a90f22 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -53,7 +53,7 @@ template 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 { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index a407d819b7..491f26f46e 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -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); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 498a443ec3..e9b0c72ecd 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -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; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index da1f6f85a7..de7ffb4f09 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -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 From 8b737aabd9ad7fd675e00aa53003ae67983e6efb Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Wed, 9 Jun 2021 19:05:27 -0700 Subject: [PATCH 004/285] Add support for waveshare_epaper 1.54v2 (#1843) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/waveshare_epaper/display.py | 3 ++- esphome/components/waveshare_epaper/waveshare_epaper.cpp | 9 ++++++++- esphome/components/waveshare_epaper/waveshare_epaper.h | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 42509f39f1..3e1132bb1d 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -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 diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 0fb783107d..daaa77b7fa 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -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: diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 0b8958e7f0..8ab77d653b 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -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); From a8c253a2a5bdf3466f00a42acffcc12f15307f04 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Jun 2021 17:17:52 +1200 Subject: [PATCH 005/285] Bump version to v1.19.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 159a74adc2..30df9abe17 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -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}" From 4b91cfb7f90f3d34de63428a1df08c30f09ab7a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 11 Jun 2021 23:28:00 +1200 Subject: [PATCH 006/285] Ensure wifi is in at least station mode before starting improv (#1899) --- esphome/components/wifi/wifi_component.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index de7ffb4f09..e98754fac7 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -79,7 +79,8 @@ void WiFiComponent::setup() { } #ifdef USE_IMPROV if (esp32_improv::global_improv_component != nullptr) - esp32_improv::global_improv_component->start(); + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); #endif this->wifi_apply_hostname_(); #if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS) @@ -143,11 +144,11 @@ void WiFiComponent::loop() { } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) { - if (!this->is_connected()) { - esp32_improv::global_improv_component->start(); - } - } + if (esp32_improv::global_improv_component != nullptr) + if (!this->is_connected()) + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); + #endif if (!this->has_ap() && this->reboot_timeout_ != 0) { From 575badc6901ad0c960b8080b0ac268e2e450c285 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 08:31:15 +1200 Subject: [PATCH 007/285] Move esp32_ble_server to its own component (#1898) --- CODEOWNERS | 1 + esphome/components/esp32_ble/__init__.py | 26 +--------- esphome/components/esp32_ble/ble.cpp | 11 +++++ esphome/components/esp32_ble/ble.h | 27 +++++++++-- .../components/esp32_ble/ble_advertising.cpp | 4 ++ .../components/esp32_ble/ble_advertising.h | 1 + .../components/esp32_ble_server/__init__.py | 39 +++++++++++++++ .../ble_2901.cpp | 8 ++-- .../ble_2901.h | 4 +- .../ble_2902.cpp | 8 ++-- .../ble_2902.h | 4 +- .../ble_characteristic.cpp | 30 ++++++------ .../ble_characteristic.h | 12 +++-- .../ble_descriptor.cpp | 6 +-- .../ble_descriptor.h | 8 ++-- .../ble_server.cpp | 39 ++++++--------- .../ble_server.h | 23 +++++---- .../ble_service.cpp | 6 +-- .../ble_service.h | 8 ++-- esphome/components/esp32_improv/__init__.py | 11 +++-- .../esp32_improv/esp32_improv_component.cpp | 48 ++++++++----------- .../esp32_improv/esp32_improv_component.h | 30 ++++++------ esphome/core/defines.h | 1 + tests/test5.yaml | 7 +-- 24 files changed, 204 insertions(+), 158 deletions(-) create mode 100644 esphome/components/esp32_ble_server/__init__.py rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.cpp (67%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.h (80%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.cpp (51%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.h (75%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.cpp (86%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.h (94%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.cpp (95%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.h (87%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.cpp (84%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.h (89%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.cpp (97%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.h (93%) diff --git a/CODEOWNERS b/CODEOWNERS index 3f1abae5df..0594a60ef6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -36,6 +36,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 6437216f69..ccf1f6cafe 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,30 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CODEOWNERS = ["@jesserockz"] - -CONF_MANUFACTURER = "manufacturer" -CONF_SERVER = "server" +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) -BLEServer = esp32_ble_ns.class_("BLEServer", cg.Component) - -BLEServiceComponent = esp32_ble_ns.class_("BLEServiceComponent") CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), - cv.Optional(CONF_SERVER): cv.Schema( - { - cv.GenerateID(): cv.declare_id(BLEServer), - cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, - cv.Optional(CONF_MODEL): cv.string, - } - ), } ).extend(cv.COMPONENT_SCHEMA) @@ -32,13 +20,3 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - - if CONF_SERVER in config: - conf = config[CONF_SERVER] - server = cg.new_Pvariable(conf[CONF_ID]) - await cg.register_component(server, conf) - cg.add(server.set_manufacturer(conf[CONF_MANUFACTURER])) - if CONF_MODEL in conf: - cg.add(server.set_model(conf[CONF_MODEL])) - cg.add_define("USE_ESP32_BLE_SERVER") - cg.add(var.set_server(server)) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 889c793017..18e80754df 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,4 +1,5 @@ #include "ble.h" + #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -26,14 +27,22 @@ void ESP32BLE::setup() { return; } + this->advertising_ = new BLEAdvertising(); + + this->advertising_->set_scan_response(true); + this->advertising_->set_min_preferred_interval(0x06); + this->advertising_->start(); + ESP_LOGD(TAG, "BLE setup complete"); } void ESP32BLE::mark_failed() { Component::mark_failed(); +#ifdef USE_ESP32_BLE_SERVER if (this->server_ != nullptr) { this->server_->mark_failed(); } +#endif } bool ESP32BLE::ble_setup_() { @@ -142,7 +151,9 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); +#ifdef USE_ESP32_BLE_SERVER this->server_->gatts_event_handler(event, gatts_if, param); +#endif } void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 27cb9a2092..149f0008a4 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -1,16 +1,21 @@ #pragma once +#include "ble_advertising.h" + #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "ble_server.h" #include "queue.h" +#ifdef USE_ESP32_BLE_SERVER +#include "esphome/components/esp32_ble_server/ble_server.h" +#endif + #ifdef ARDUINO_ARCH_ESP32 #include #include #include - namespace esphome { namespace esp32_ble { @@ -28,11 +33,20 @@ class ESP32BLE : public Component { float get_setup_priority() const override; void mark_failed() override; - bool has_server() { return this->server_ != nullptr; } + bool has_server() { +#ifdef USE_ESP32_BLE_SERVER + return this->server_ != nullptr; +#else + return false; +#endif + } bool has_client() { return false; } - void set_server(BLEServer *server) { this->server_ = server; } + BLEAdvertising *get_advertising() { return this->advertising_; } +#ifdef USE_ESP32_BLE_SERVER + void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; } +#endif protected: static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); @@ -44,8 +58,11 @@ class ESP32BLE : public Component { bool ble_setup_(); - BLEServer *server_{nullptr}; +#ifdef USE_ESP32_BLE_SERVER + esp32_ble_server::BLEServer *server_{nullptr}; +#endif Queue ble_events_; + BLEAdvertising *advertising_; }; extern ESP32BLE *global_ble; diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 6162bf3a40..f215fb48a3 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -32,6 +32,10 @@ BLEAdvertising::BLEAdvertising() { } void BLEAdvertising::add_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.push_back(uuid); } +void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { + this->advertising_uuids_.erase(std::remove(this->advertising_uuids_.begin(), this->advertising_uuids_.end(), uuid), + this->advertising_uuids_.end()); +} void BLEAdvertising::start() { int num_services = this->advertising_uuids_.size(); diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 405edbf482..d86089f333 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -17,6 +17,7 @@ class BLEAdvertising { BLEAdvertising(); void add_service_uuid(ESPBTUUID uuid); + void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py new file mode 100644 index 0000000000..0dcbcadc50 --- /dev/null +++ b/esphome/components/esp32_ble_server/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.components import esp32_ble + +AUTO_LOAD = ["esp32_ble"] +CODEOWNERS = ["@jesserockz"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONF_MANUFACTURER = "manufacturer" +CONF_BLE_ID = "ble_id" + +esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server") +BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component) +BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent") + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(BLEServer), + cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), + cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, + cv.Optional(CONF_MODEL): cv.string, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_BLE_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + cg.add(var.set_manufacturer(config[CONF_MANUFACTURER])) + if CONF_MODEL in config: + cg.add(var.set_model(config[CONF_MODEL])) + cg.add_define("USE_ESP32_BLE_SERVER") + + cg.add(parent.set_server(var)) diff --git a/esphome/components/esp32_ble/ble_2901.cpp b/esphome/components/esp32_ble_server/ble_2901.cpp similarity index 67% rename from esphome/components/esp32_ble/ble_2901.cpp rename to esphome/components/esp32_ble_server/ble_2901.cpp index 952d82d5f0..962ba3ffbe 100644 --- a/esphome/components/esp32_ble/ble_2901.cpp +++ b/esphome/components/esp32_ble_server/ble_2901.cpp @@ -1,18 +1,18 @@ #include "ble_2901.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { BLE2901::BLE2901(const std::string &value) : BLE2901((uint8_t *) value.data(), value.length()) {} -BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(ESPBTUUID::from_uint16(0x2901)) { +BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2901)) { this->set_value(data, length); this->permissions_ = ESP_GATT_PERM_READ; } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2901.h b/esphome/components/esp32_ble_server/ble_2901.h similarity index 80% rename from esphome/components/esp32_ble/ble_2901.h rename to esphome/components/esp32_ble_server/ble_2901.h index e606b6271a..3bb23ae69d 100644 --- a/esphome/components/esp32_ble/ble_2901.h +++ b/esphome/components/esp32_ble_server/ble_2901.h @@ -5,7 +5,7 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2901 : public BLEDescriptor { public: @@ -13,7 +13,7 @@ class BLE2901 : public BLEDescriptor { BLE2901(const uint8_t *data, size_t length); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.cpp b/esphome/components/esp32_ble_server/ble_2902.cpp similarity index 51% rename from esphome/components/esp32_ble/ble_2902.cpp rename to esphome/components/esp32_ble_server/ble_2902.cpp index 164c8f40ff..0a87b239f9 100644 --- a/esphome/components/esp32_ble/ble_2902.cpp +++ b/esphome/components/esp32_ble_server/ble_2902.cpp @@ -1,18 +1,18 @@ #include "ble_2902.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -BLE2902::BLE2902() : BLEDescriptor(ESPBTUUID::from_uint16(0x2902)) { +BLE2902::BLE2902() : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2902)) { this->value_.attr_len = 2; uint8_t data[2] = {0, 0}; memcpy(this->value_.attr_value, data, 2); } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.h b/esphome/components/esp32_ble_server/ble_2902.h similarity index 75% rename from esphome/components/esp32_ble/ble_2902.h rename to esphome/components/esp32_ble_server/ble_2902.h index 356a0bb107..024eec755e 100644 --- a/esphome/components/esp32_ble/ble_2902.h +++ b/esphome/components/esp32_ble_server/ble_2902.h @@ -5,14 +5,14 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2902 : public BLEDescriptor { public: BLE2902(); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp similarity index 86% rename from esphome/components/esp32_ble/ble_characteristic.cpp rename to esphome/components/esp32_ble_server/ble_characteristic.cpp index 127f973146..23b2693839 100644 --- a/esphome/components/esp32_ble/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.characteristic"; +static const char *const TAG = "esp32_ble_server.characteristic"; BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) { this->set_value_lock_ = xSemaphoreCreateBinary(); @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, @@ -300,7 +300,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h similarity index 94% rename from esphome/components/esp32_ble/ble_characteristic.h rename to esphome/components/esp32_ble_server/ble_characteristic.h index 648cfb939d..bc5033f2ae 100644 --- a/esphome/components/esp32_ble/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -1,9 +1,9 @@ #pragma once -#include - -#include "ble_uuid.h" #include "ble_descriptor.h" +#include "esphome/components/esp32_ble/ble_uuid.h" + +#include #ifdef ARDUINO_ARCH_ESP32 @@ -14,7 +14,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEService; @@ -89,7 +91,7 @@ class BLECharacteristic { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp similarity index 95% rename from esphome/components/esp32_ble/ble_descriptor.cpp rename to esphome/components/esp32_ble_server/ble_descriptor.cpp index 9738cc2fe7..a04343da3a 100644 --- a/esphome/components/esp32_ble/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.descriptor"; +static const char *const TAG = "esp32_ble_server.descriptor"; BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->uuid_ = uuid; @@ -71,7 +71,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h similarity index 87% rename from esphome/components/esp32_ble/ble_descriptor.h rename to esphome/components/esp32_ble_server/ble_descriptor.h index bc7b48e331..1a72cb2b54 100644 --- a/esphome/components/esp32_ble/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,6 +1,6 @@ #pragma once -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -8,7 +8,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLECharacteristic; @@ -43,7 +45,7 @@ class BLEDescriptor { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp similarity index 84% rename from esphome/components/esp32_ble/ble_server.cpp rename to esphome/components/esp32_ble_server/ble_server.cpp index 9d5be46113..db83eb6bee 100644 --- a/esphome/components/esp32_ble/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -1,5 +1,6 @@ #include "ble_server.h" -#include "ble.h" + +#include "esphome/components/esp32_ble/ble.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/version.h" @@ -14,11 +15,11 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.server"; +static const char *const 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,8 +33,6 @@ void BLEServer::setup() { ESP_LOGD(TAG, "Setting up BLE Server..."); global_ble_server = this; - - this->advertising_ = new BLEAdvertising(); } void BLEServer::loop() { @@ -53,35 +52,27 @@ void BLEServer::loop() { } case REGISTERING: { if (this->registered_) { - this->device_information_service_ = this->create_service(device_information_service__UUID); + 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_created()) { + break; + } if (this->device_information_service_->is_running()) { - for (auto *component : this->service_components_) { - component->setup_service(); - } - this->state_ = SETTING_UP_COMPONENT_SERVICES; + this->state_ = RUNNING; + this->can_proceed_ = true; + ESP_LOGD(TAG, "BLE server setup successfully"); } 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; - } } } @@ -123,7 +114,7 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n BLEService *service = new BLEService(uuid, num_handles, inst_id); this->services_.push_back(service); if (advertise) { - this->advertising_->add_service_uuid(uuid); + esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } service->do_create(this); return service; @@ -145,7 +136,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga ESP_LOGD(TAG, "BLE Client disconnected"); if (this->remove_client_(param->disconnect.conn_id)) this->connected_clients_--; - this->advertising_->start(); + esp32_ble::global_ble->get_advertising()->start(); for (auto *component : this->service_components_) { component->on_client_disconnect(); } @@ -171,7 +162,7 @@ void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } BLEServer *global_ble_server = nullptr; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h similarity index 89% rename from esphome/components/esp32_ble/ble_server.h rename to esphome/components/esp32_ble_server/ble_server.h index 5657773446..9d955dda79 100644 --- a/esphome/components/esp32_ble/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -1,15 +1,16 @@ #pragma once +#include "ble_service.h" +#include "ble_characteristic.h" + +#include "esphome/components/esp32_ble/ble_advertising.h" +#include "esphome/components/esp32_ble/ble_uuid.h" +#include "esphome/components/esp32_ble/queue.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "ble_service.h" -#include "ble_characteristic.h" -#include "ble_uuid.h" -#include "ble_advertising.h" -#include -#include "queue.h" +#include #ifdef ARDUINO_ARCH_ESP32 @@ -17,11 +18,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEServiceComponent { public: - virtual void setup_service(); virtual void on_client_connect(){}; virtual void on_client_disconnect(){}; virtual void start(); @@ -49,7 +51,6 @@ class BLEServer : public Component { esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } const std::map &get_clients() { return this->clients_; } - BLEAdvertising *get_advertising() { return this->advertising_; } void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -69,7 +70,6 @@ class BLEServer : public Component { optional model_; esp_gatt_if_t gatts_if_{0}; bool registered_{false}; - BLEAdvertising *advertising_; uint32_t connected_clients_{0}; std::map clients_; @@ -83,14 +83,13 @@ class BLEServer : public Component { INIT = 0x00, REGISTERING, STARTING_SERVICE, - SETTING_UP_COMPONENT_SERVICES, RUNNING, } state_{INIT}; }; extern BLEServer *global_ble_server; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp similarity index 97% rename from esphome/components/esp32_ble/ble_service.cpp rename to esphome/components/esp32_ble_server/ble_service.cpp index 4b85182696..b7c88a436c 100644 --- a/esphome/components/esp32_ble/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -5,9 +5,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.service"; +static const char *const TAG = "esp32_ble_server.service"; BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {} @@ -136,7 +136,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h similarity index 93% rename from esphome/components/esp32_ble/ble_service.h rename to esphome/components/esp32_ble_server/ble_service.h index a539631846..9fdb95bde5 100644 --- a/esphome/components/esp32_ble/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -1,7 +1,7 @@ #pragma once -#include "ble_uuid.h" #include "ble_characteristic.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,10 +12,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLEServer; +using namespace esp32_ble; + class BLEService { public: BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id); @@ -73,7 +75,7 @@ class BLEService { } running_state_{STOPPED}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 8b91e9151d..4c337055cb 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,12 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor, output, esp32_ble +from esphome.components import binary_sensor, output, esp32_ble_server from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 -AUTO_LOAD = ["binary_sensor", "output", "improv"] +AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["esp32_ble", "wifi"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +DEPENDENCIES = ["wifi"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CONF_AUTHORIZED_DURATION = "authorized_duration" @@ -18,7 +19,7 @@ CONF_WIFI_TIMEOUT = "wifi_timeout" esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv") ESP32ImprovComponent = esp32_improv_ns.class_( - "ESP32ImprovComponent", cg.Component, esp32_ble.BLEServiceComponent + "ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent ) @@ -33,7 +34,7 @@ def validate_none_(value): CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), - cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble.BLEServer), + cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), cv.Required(CONF_AUTHORIZER): cv.Any( validate_none_, cv.use_id(binary_sensor.BinarySensor) ), diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 491f26f46e..e076936771 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -1,7 +1,9 @@ #include "esp32_improv_component.h" -#include "esphome/core/log.h" + +#include "esphome/components/esp32_ble/ble.h" +#include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/core/application.h" -#include "esphome/components/esp32_ble/ble_2902.h" +#include "esphome/core/log.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,40 +14,39 @@ static const char *TAG = "esp32_improv.component"; 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() { + this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); + this->setup_characteristics(); } 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(); + improv::STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *status_descriptor = new BLE2902(); this->status_->add_descriptor(status_descriptor); this->error_ = this->service_->create_characteristic( - improv::ERROR_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *error_descriptor = new esp32_ble::BLE2902(); + improv::ERROR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *error_descriptor = new BLE2902(); this->error_->add_descriptor(error_descriptor); - this->rpc_ = - this->service_->create_characteristic(improv::RPC_COMMAND_UUID, esp32_ble::BLECharacteristic::PROPERTY_WRITE); + this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_->on_write([this](const std::vector &data) { if (data.size() > 0) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } }); - esp32_ble::BLEDescriptor *rpc_descriptor = new esp32_ble::BLE2902(); + BLEDescriptor *rpc_descriptor = new BLE2902(); this->rpc_->add_descriptor(rpc_descriptor); - this->rpc_response_ = - this->service_->create_characteristic(improv::RPC_RESULT_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | - esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *rpc_response_descriptor = new esp32_ble::BLE2902(); + this->rpc_response_ = this->service_->create_characteristic( + improv::RPC_RESULT_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *rpc_response_descriptor = new BLE2902(); this->rpc_response_->add_descriptor(rpc_response_descriptor); this->capabilities_ = - this->service_->create_characteristic(improv::CAPABILITIES_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ); - esp32_ble::BLEDescriptor *capabilities_descriptor = new esp32_ble::BLE2902(); + this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ); + BLEDescriptor *capabilities_descriptor = new BLE2902(); this->capabilities_->add_descriptor(capabilities_descriptor); uint8_t capabilities = 0x00; if (this->status_indicator_ != nullptr) @@ -64,13 +65,9 @@ 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_) { + if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_running()) { - this->service_->get_server()->get_advertising()->start(); + esp32_ble::global_ble->get_advertising()->start(); this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); this->set_error_(improv::ERROR_NONE); @@ -205,10 +202,7 @@ void ESP32ImprovComponent::stop() { }); } -float ESP32ImprovComponent::get_setup_priority() const { - // Before WiFi - return setup_priority::AFTER_BLUETOOTH; -} +float ESP32ImprovComponent::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void ESP32ImprovComponent::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Improv:"); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index e9b0c72ecd..262124f983 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,26 +1,28 @@ #pragma once +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/esp32_ble_server/ble_server.h" +#include "esphome/components/esp32_ble_server/ble_characteristic.h" +#include "esphome/components/improv/improv.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/esp32_ble/ble_server.h" -#include "esphome/components/esp32_ble/ble_characteristic.h" -#include "esphome/components/output/binary_output.h" -#include "esphome/components/wifi/wifi_component.h" -#include "esphome/components/improv/improv.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { namespace esp32_improv { -class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceComponent { +using namespace esp32_ble_server; + +class ESP32ImprovComponent : public Component, public BLEServiceComponent { public: ESP32ImprovComponent(); void dump_config() override; void loop() override; - void setup_service() override; + void setup() override; void setup_characteristics(); void on_client_disconnect() override; @@ -46,12 +48,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - esp32_ble::BLEService *service_; - esp32_ble::BLECharacteristic *status_; - esp32_ble::BLECharacteristic *error_; - esp32_ble::BLECharacteristic *rpc_; - esp32_ble::BLECharacteristic *rpc_response_; - esp32_ble::BLECharacteristic *capabilities_; + BLEService *service_; + BLECharacteristic *status_; + BLECharacteristic *error_; + BLECharacteristic *rpc_; + BLECharacteristic *rpc_response_; + BLECharacteristic *capabilities_; binary_sensor::BinarySensor *authorizer_{nullptr}; output::BinaryOutput *status_indicator_{nullptr}; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 24efd0fd14..90562510b9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,6 +19,7 @@ #define USE_JSON #ifdef ARDUINO_ARCH_ESP32 #define USE_ESP32_CAMERA +#define USE_ESP32_BLE_SERVER #define USE_IMPROV #endif #define USE_TIME diff --git a/tests/test5.yaml b/tests/test5.yaml index d4e19d985b..ba047721e2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -29,9 +29,10 @@ output: id: built_in_led esp32_ble: - server: - manufacturer: "ESPHome" - model: "Test5" + +esp32_ble_server: + manufacturer: "ESPHome" + model: "Test5" esp32_improv: authorizer: io0_button From 9bb64315f3385cb8fe704349a69e1bfb43c16a98 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Jun 2021 15:49:05 -0700 Subject: [PATCH 008/285] Add new wizard + allow installing firmware over webserial (#1887) --- esphome/dashboard/dashboard.py | 68 +- esphome/dashboard/static/css/esphome.css | 398 ------- .../materialize-stepper.min.css | 10 - .../vendor/materialize/materialize.min.css | 13 - .../static/fonts/material-icons/LICENSE | 202 ---- .../material-icons/MaterialIcons-Regular.woff | Bin 57620 -> 0 bytes .../MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes .../static/fonts/material-icons/README.md | 12 - .../fonts/material-icons/material-icons.css | 34 - esphome/dashboard/static/images/favicon.ico | Bin 15086 -> 0 bytes esphome/dashboard/static/js/esphome.js | 1021 ----------------- esphome/dashboard/static/js/vendor/ace/ace.js | 16 - .../static/js/vendor/ace/ext-searchbox.js | 7 - .../static/js/vendor/ace/mode-yaml.js | 7 - .../static/js/vendor/ace/theme-dreamweaver.js | 7 - .../js/vendor/jquery-ui/jquery-ui.min.js | 13 - .../jquery-validate/jquery.validate.min.js | 4 - .../static/js/vendor/jquery/jquery.min.js | 2 - .../materialize-stepper.min.js | 10 - .../js/vendor/materialize/materialize.min.js | 6 - esphome/dashboard/templates/index.html | 678 ----------- esphome/dashboard/templates/login.html | 93 -- esphome/wizard.py | 56 +- requirements.txt | 1 + 24 files changed, 98 insertions(+), 2560 deletions(-) delete mode 100644 esphome/dashboard/static/css/esphome.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize/materialize.min.css delete mode 100644 esphome/dashboard/static/fonts/material-icons/LICENSE delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 delete mode 100644 esphome/dashboard/static/fonts/material-icons/README.md delete mode 100644 esphome/dashboard/static/fonts/material-icons/material-icons.css delete mode 100644 esphome/dashboard/static/images/favicon.ico delete mode 100644 esphome/dashboard/static/js/esphome.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ace.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ext-searchbox.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/mode-yaml.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-validate/jquery.validate.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery/jquery.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize-stepper/materialize-stepper.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize/materialize.min.js delete mode 100644 esphome/dashboard/templates/index.html delete mode 100644 esphome/dashboard/templates/login.html diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 4b40237768..90061a3d4e 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +import secrets import shutil import subprocess import threading @@ -44,6 +45,8 @@ from esphome.zeroconf import DashboardStatus, Zeroconf _LOGGER = logging.getLogger(__name__) +ENV_DEV = "ESPHOME_DASHBOARD_DEV" + class DashboardSettings: def __init__(self): @@ -111,6 +114,7 @@ def template_args(): docs_link = "https://next.esphome.io/" else: docs_link = "https://www.esphome.io/" + return { "version": version, "docs_link": docs_link, @@ -349,6 +353,7 @@ class SerialPortRequestHandler(BaseHandler): data.append({"port": port.path, "desc": desc}) data.append({"port": "OTA", "desc": "Over-The-Air"}) data.sort(key=lambda x: x["port"], reverse=True) + self.set_header("content-type", "application/json") self.write(json.dumps(data)) @@ -358,11 +363,15 @@ class WizardRequestHandler(BaseHandler): from esphome import wizard kwargs = { - k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items() + k: "".join(x.decode() for x in v) + for k, v in self.request.arguments.items() + if k in ("name", "platform", "board", "ssid", "psk", "password") } + kwargs["ota_password"] = secrets.token_hex(16) destination = settings.rel_path(kwargs["name"] + ".yaml") wizard.wizard_write(path=destination, **kwargs) - self.redirect("./?begin=True") + self.set_status(200) + self.finish() class DownloadBinaryRequestHandler(BaseHandler): @@ -473,7 +482,7 @@ class MainRequestHandler(BaseHandler): entries = _list_dashboard_entries() self.render( - "templates/index.html", + get_template_path("index"), entries=entries, begin=begin, **template_args(), @@ -560,6 +569,7 @@ class PingRequestHandler(BaseHandler): @authenticated def get(self): PING_REQUEST.set() + self.set_header("content-type", "application/json") self.write(json.dumps(PING_RESULT)) @@ -567,6 +577,21 @@ def is_allowed(configuration): return os.path.sep not in configuration +class InfoRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + yaml_path = settings.rel_path(configuration) + all_yaml_files = settings.list_yaml_files() + + if yaml_path not in all_yaml_files: + self.set_status(404) + return + + self.set_header("content-type", "application/json") + self.write(DashboardEntry(yaml_path).storage.to_json()) + + class EditRequestHandler(BaseHandler): @authenticated @bind_config @@ -633,7 +658,7 @@ class LoginHandler(BaseHandler): def render_login_page(self, error=None): self.render( - "templates/login.html", + get_template_path("login"), error=error, hassio=settings.using_hassio_auth, has_username=bool(settings.username), @@ -694,19 +719,44 @@ class LogoutHandler(BaseHandler): _STATIC_FILE_HASHES = {} +def get_base_frontend_path(): + if ENV_DEV not in os.environ: + import esphome_dashboard + + return esphome_dashboard.where() + + static_path = os.environ[ENV_DEV] + if not static_path.endswith("/"): + static_path += "/" + + # This path can be relative, so resolve against the root or else templates don't work + return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard")) + + +def get_template_path(template_name): + return os.path.join(get_base_frontend_path(), f"{template_name}.template.html") + + +def get_static_path(*args): + return os.path.join(get_base_frontend_path(), "static", *args) + + def get_static_file_url(name): - static_path = os.path.join(os.path.dirname(__file__), "static") + # Module imports can't deduplicate if stuff added to url + if name == "js/esphome/index.js": + return f"./static/{name}" + if name in _STATIC_FILE_HASHES: hash_ = _STATIC_FILE_HASHES[name] else: - path = os.path.join(static_path, name) + path = get_static_path(name) with open(path, "rb") as f_handle: hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] _STATIC_FILE_HASHES[name] = hash_ return f"./static/{name}?hash={hash_}" -def make_app(debug=False): +def make_app(debug=get_bool_env(ENV_DEV)): def log_function(handler): if handler.get_status() < 400: log_method = access_log.info @@ -736,7 +786,6 @@ def make_app(debug=False): "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" ) - static_path = os.path.join(os.path.dirname(__file__), "static") app_settings = { "debug": debug, "cookie_secret": settings.cookie_secret, @@ -758,6 +807,7 @@ def make_app(debug=False): (rel + "vscode", EsphomeVscodeHandler), (rel + "ace", EsphomeAceEditorHandler), (rel + "update-all", EsphomeUpdateAllHandler), + (rel + "info", InfoRequestHandler), (rel + "edit", EditRequestHandler), (rel + "download.bin", DownloadBinaryRequestHandler), (rel + "serial-ports", SerialPortRequestHandler), @@ -765,7 +815,7 @@ def make_app(debug=False): (rel + "delete", DeleteRequestHandler), (rel + "undo-delete", UndoDeleteRequestHandler), (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {"path": static_path}), + (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}), ], **app_settings, ) diff --git a/esphome/dashboard/static/css/esphome.css b/esphome/dashboard/static/css/esphome.css deleted file mode 100644 index 116170f95d..0000000000 --- a/esphome/dashboard/static/css/esphome.css +++ /dev/null @@ -1,398 +0,0 @@ -/* Base */ - -:root { - /* Colors */ - --primary-bg-color: #fafafa; - - --alert-standard-color: #666666; - --alert-standard-color-bg: #e6e6e6; - --alert-info-color: #00539f; - --alert-info-color-bg: #E6EEF5; - --alert-success-color: #4CAF50; - --alert-success-color-bg: #EDF7EE; - --alert-warning-color: #FF9800; - --alert-warning-color-bg: #FFF5E6; - --alert-error-color: #D93025; - --alert-error-color-bg: #FAEFEB; -} - -body { - display: flex; - min-height: 100vh; - flex-direction: column; - background-color: var(--primary-bg-color); -} - -/* Layout */ -.valign-wrapper { - position: absolute; - width:100vw; - height:100vh; -} - -.valign { - width: 100%; -} - -main { - flex: 1 0 auto; -} - -/* Alerts & Errors */ -.alert { - width: 100%; - margin: 10px auto; - padding: 10px; - border-radius: 2px; - border-left-width: 4px; - border-left-style: solid; -} - -.alert .title { - font-weight: bold; -} - -.alert .title::after { - content: "\A"; - white-space: pre; -} - -.alert.alert-error { - color: var(--alert-error-color); - border-left-color: var(--alert-error-color); - background-color: var(--alert-error-color-bg); -} - -.card.card-error, .card.status-offline { - border-top: 4px solid var(--alert-error-color); -} - -.card.status-online { - border-top: 4px solid var(--alert-success-color); -} - -.card.status-not-responding { - border-top: 4px solid var(--alert-warning-color); -} - -.card.status-unknown { - border-top: 4px solid var(--alert-standard-color); -} - -/* Login Page */ -#login-page .row.no-bottom-margin { - margin-bottom: 0 !important; -} - -#login-page .logo { - display: block; - width: auto; - max-width: 300px; - margin-left: auto; - margin-right: auto; -} - -#login-page .input-field input:focus + label { - color: #000; -} - -#login-page .input-field input:focus { - border-bottom: 1px solid #000; - box-shadow: 0 1px 0 0 #000; -} - -#login-page .input-field .prefix.active { - color: #000; -} - -#login-page .version-number { - display: block; - text-align: center; - margin-bottom: 20px;; - color:#808080; - font-size: 12px; -} - -#login-page footer { - color: #757575; - font-size: 12px; -} - -#login-page footer a { - color: #424242; -} - -#login-page footer p { - -webkit-margin-before: 0px; - margin-block-start: 0px; - -webkit-margin-after: 5px; - margin-block-end: 5px; -} - -#login-page footer p:last-child { - -webkit-margin-after: 0px; - margin-block-end: 0px; -} - -/* Dashboard */ -.logo-wrapper { - height: 64px; - height: 100%; - width: 0; - margin-left: 24px; -} - -.logo { - width: auto; - height: 48px; - margin: 8px 0; -} - -@media only screen and (max-width: 601px) { - .logo { - height: 38px; - margin: 9px 0; - } -} - -.nav-icons { - margin-right: 24px; -} - -.nav-icons i { - color: black; -} - -nav { - height: auto; - line-height: normal; -} - -.select-port-container { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select .select-dropdown { - color: black; -} - -.serial-port-select .select-dropdown:focus { - border-bottom: 1px solid #607d8b !important; -} - -.serial-port-select .caret { - fill: black; -} - -.serial-port-select .dropdown-content li>span { - color: black; -} - -#nav-dropdown li a, .node-dropdown li a { - color: black; -} - -main .container { - margin-top: 20px; - margin-bottom: 20px; - width: 90%; - max-width: 1920px; -} - -#nodes .card-content { - height: calc(100% - 47px); -} - -#nodes .card-content, #nodes .card-action { - padding: 12px; -} - -#nodes .grid-1-col { - display: grid; - grid-template-columns: 1fr; -} - -#nodes .grid-2-col { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; -} - -#nodes .grid-3-col { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-column-gap: 1.5rem; -} - -@media only screen and (max-width: 1100px) { - #nodes .grid-3-col { - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; - } -} - -@media only screen and (max-width: 750px) { - #nodes .grid-2-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } - - #nodes .grid-3-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } -} - -i.node-update-avaliable { - color:#3f51b5; -} - -i.node-webserver { - color:#039be5; -} - -.node-config-path { - margin-top: -8px; - margin-bottom: 8px; - font-size: 14px; -} - -.node-card-comment { - color: #444; - font-style: italic; -} - -.card-action a, .card-dropdown-action a { - cursor: pointer; -} - -.tooltipped { - cursor: help; -} - -#js-loading-indicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.editor { - margin-top: 0; - margin-bottom: 0; - border-radius: 3px; - height: calc(100% - 56px); -} - -.inlinecode { - box-sizing: border-box; - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: rgba(27,31,35,0.05); - border-radius: 3px; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; -} - -.log { - height: 100%; - max-height: calc(100% - 56px); - background-color: #1c1c1c; - margin-top: 0; - margin-bottom: 0; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - padding: 16px; - overflow: auto; - line-height: 1.45; - border-radius: 3px; - white-space: pre-wrap; - overflow-wrap: break-word; - color: #DDD; -} - -.log-bold { font-weight: bold; } -.log-italic { font-style: italic; } -.log-underline { text-decoration: underline; } -.log-strikethrough { text-decoration: line-through; } -.log-underline.log-strikethrough { text-decoration: underline line-through; } -.log-secret { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.log-secret-redacted { - opacity: 0; - width: 1px; - font-size: 1px; -} -.log-fg-black { color: rgb(128,128,128); } -.log-fg-red { color: rgb(255,0,0); } -.log-fg-green { color: rgb(0,255,0); } -.log-fg-yellow { color: rgb(255,255,0); } -.log-fg-blue { color: rgb(0,0,255); } -.log-fg-magenta { color: rgb(255,0,255); } -.log-fg-cyan { color: rgb(0,255,255); } -.log-fg-white { color: rgb(187,187,187); } -.log-bg-black { background-color: rgb(0,0,0); } -.log-bg-red { background-color: rgb(255,0,0); } -.log-bg-green { background-color: rgb(0,255,0); } -.log-bg-yellow { background-color: rgb(255,255,0); } -.log-bg-blue { background-color: rgb(0,0,255); } -.log-bg-magenta { background-color: rgb(255,0,255); } -.log-bg-cyan { background-color: rgb(0,255,255); } -.log-bg-white { background-color: rgb(255,255,255); } - -ul.browser-default { - padding-left: 30px; - margin-top: 10px; - margin-bottom: 15px; -} - -ul.browser-default li { - list-style-type: initial; -} - -ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before { - background-color: #3f51b5 !important; -} - -.select-action { - width: auto !important; - height: auto !important; - white-space: nowrap; -} - -.modal { - width: 95%; - max-height: 90%; - height: 85% !important; -} - -.page-footer { - display: flex; - align-items: center; - min-height: 50px; - padding-top: 0; - color: grey; -} - -.page-footer a { - color: #afafaf; -} - -@media only screen and (max-width: 992px) { - .page-footer .left, .page-footer .right { - width: 100%; - text-align: center; - } -} diff --git a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css b/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css deleted file mode 100644 index 390f16a011..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Materialize Stepper - A little plugin that implements a stepper to Materializecss framework. - * @version v3.1.0 - * @author Igor Marcossi (Kinark) . - * @link https://github.com/Kinark/Materialize-stepper - * - * Licensed under the MIT License (https://github.com/Kinark/Materialize-stepper/blob/master/LICENSE). - */ - - .card-content ul.stepper{margin:1em -24px;padding:0 24px}@media only screen and (min-width:993px){.card-content ul.stepper.horizontal{margin-left:-24px;margin-right:-24px;padding-left:24px;padding-right:24px}.card-content ul.stepper.horizontal:first-child{margin-top:-24px}.card-content ul.stepper.horizontal .step.step-content{padding-left:40px;padding-right:40px}.card-content ul.stepper.horizontal .step.step-content .step-actions{padding-left:40px;padding-right:40px}}ul.stepper{counter-reset:section;overflow-y:auto;overflow-x:hidden}ul.stepper .wait-feedback{left:0;right:0;top:0;z-index:2;position:absolute;width:100%;height:100%;text-align:center;display:flex;justify-content:center;align-items:center}ul.stepper .step{position:relative;transition:height .4s cubic-bezier(.4,0,.2,1),padding-bottom .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step .step-title{margin:0 -24px;cursor:pointer;padding:15.5px 44px 24px 64px;display:block}ul.stepper .step .step-title:hover{background-color:rgba(0,0,0,.06)}ul.stepper .step .step-title::after{content:attr(data-step-label);display:block;position:absolute;font-size:12.8px;font-size:.8rem;color:#424242;font-weight:400}ul.stepper .step .step-content{position:relative;display:none;height:0;transition:height .4s cubic-bezier(.4,0,.2,1);width:inherit;overflow:visible;margin-left:41px;margin-right:24px}ul.stepper .step .step-content .step-actions{padding-top:16px;padding-bottom:4px;display:flex;justify-content:flex-start}ul.stepper .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper .step .step-content .step-actions .btn:not(:last-child){margin-right:5px}ul.stepper .step .step-content .row{margin-bottom:7px}ul.stepper .step::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:13px}ul.stepper .step.active .step-title{font-weight:500}ul.stepper .step.active .step-content{height:auto;display:block}ul.stepper .step.active::before,ul.stepper .step.done::before{background-color:#2196f3}ul.stepper .step.done::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper .step.wrong::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper .step.feedbacking .step-content>:not(.wait-feedback){opacity:.1}ul.stepper .step:not(:last-of-type)::after{content:'';position:absolute;top:52px;left:13.5px;width:1px;height:40%;height:calc(100% - 52px);background-color:rgba(0,0,0,.1);transition:height .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step:not(:last-of-type).active{padding-bottom:36px}ul.stepper>li:not(:last-of-type){padding-bottom:10px}@media only screen and (min-width:993px){ul.stepper.horizontal{position:relative;display:flex;justify-content:space-between;min-height:458px;overflow:hidden}ul.stepper.horizontal::before{content:'';background-color:transparent;width:100%;min-height:84px;box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:absolute;left:0}ul.stepper.horizontal .step{position:static;padding:0!important;width:100%;display:flex;align-items:center;height:84px}ul.stepper.horizontal .step::before{content:none}ul.stepper.horizontal .step:last-of-type{width:auto!important}ul.stepper.horizontal .step.active:not(:last-of-type)::after,ul.stepper.horizontal .step:not(:last-of-type)::after{content:'';position:static;display:inline-block;width:100%;height:1px}ul.stepper.horizontal .step .step-title{line-height:84px;height:84px;margin:0;padding:0 25px 0 65px;display:inline-block;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0}ul.stepper.horizontal .step .step-title::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:28.5px;left:19px}ul.stepper.horizontal .step .step-title::after{top:15px}ul.stepper.horizontal .step.active~.step .step-content{left:100%}ul.stepper.horizontal .step.active .step-content{left:0!important}ul.stepper.horizontal .step.active .step-title::before,ul.stepper.horizontal .step.done .step-title::before{background-color:#2196f3}ul.stepper.horizontal .step.done .step-title::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper.horizontal .step.wrong .step-title::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper.horizontal .step .step-content{position:absolute;height:calc(100% - 84px);top:84px;display:block;left:-100%;width:100%;overflow-y:auto;overflow-x:hidden;margin:0;padding:20px 20px 76px 20px;transition:left .4s cubic-bezier(.4,0,.2,1)}ul.stepper.horizontal .step .step-content .step-actions{position:absolute;bottom:0;left:0;width:100%;padding:20px;background-color:transparent;flex-direction:row-reverse}ul.stepper.horizontal .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn:not(:last-child){margin-left:5px;margin-right:0}} diff --git a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css b/esphome/dashboard/static/css/vendor/materialize/materialize.min.css deleted file mode 100644 index 74b1741b62..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Materialize v1.0.0 (http://materializecss.com) - * Copyright 2014-2017 Materialize - * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) - */ -.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! - * Waves v0.6.0 - * http://fian.my.id/Waves - * - * Copyright 2014 Alfiana E. Sibuea and other contributors - * Released under the MIT license - * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/esphome/dashboard/static/fonts/material-icons/LICENSE b/esphome/dashboard/static/fonts/material-icons/LICENSE deleted file mode 100644 index 7a4a3ea242..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff deleted file mode 100644 index b648a3eea2d16b6ce783906d6b7d5f251b9eb56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57620 zcmY&^NelVwr$(CZQHhO+t!`$=Dp;-onGnG%1YJl`q9)OmoxnxQ~!cx z7yTwvL_vxFmrDfzAms%BFq1u;FO!o|pk)96AY1*_{QHG2qyvG0ft8*u0022U001yH z001b^-7WpDiJrqRN5%B30sjv_KLEfcmTtzs92WpU*)#y4J?2lST9B!co*@9hGW4&8 z`4=pp>u1uYzvM6XUw$aRAo>Fc^vBf7(e;Ws_PPwU|4;c6vAY`D4U;s#9fGPn0SECQP7GZX@2I3WUo4pB*5bE|8|@Fm_rEMeislDJkxA(b z7tCUlVW`i$#DWbQZsJMnX?Wci4^U?JYSLP9^{854ZTD(mZmHb5Kg#0WKDy&x2*LAw zTo>W>_}n7h_S_HghvODJCnAQCPwY%2)^GlIWGK?6;jNOlF0WOptuo*kv8|j_g}1_c zE+(DP(B{zS(DhLNP{BA|<)Y%`;w0l_Q6WO2EZKL|*ys_L#EFFrpqv(C%GE%Zc>Y>~HgyL!|@;oHhHQP}pO{tpwUsv%B#6 zd!u<`WFA2+30r%fO!U*(zhn@xA;rJNv7)dPqcC&`Gkpup)6p#8t-&S%`VH#+Vw47 z1ZrYVoekY6m!+MmkfSl@=(83Jh>RM=6@_BZ@#m2@gjSQDm~M#;i*tlcAUFkg;=PQs zMJnWEk_2tyBE8hNCL`jfI6N%DY2a%&bpE?0I6k{55d>M94FoUL_axD8r2MZ;xv-@Hvaw zq9i|4u;P4|nOd?89&S@e7$fg9w5ik7{;s1p<$%{Px^pXA)ZiJ*T_`9A%ZsrKN$)%D ztOb7M#2uWj)1nwnb0-iLgR~WM*q`jEA@w~(cU<3;TcGz6UD5z$GW#O`20df8;pRVY zzoC4zzo)g|0FvRy)=K0+BCPi)KabsDwpTdF%AsoFeo@XLYf`R3tW(N(V4APa8VTqO zYaFp!PT=^&)H+bv3U5T*5vk{AeXej$R;Oewpd^)uVn0)o;zmt7lRTM9REl*{mONZN z<|S<4WFKxe0$E{t$xn2nCGWG0$W{E${W(Sw*BQ{1U**^A&8 zI$rVs&Q8tZEFBp*nancPz{--(mmK4uN7@+{1uq?=-Qk{v}Ai(*JQ<Qb) ziI9oKiR_8ziS&uliH3S=!6yBgeC6Harr>SJm)-bB1PpopT0sz{MF16qoR^V~HVCLue&LVU6e$yTtP$;v!eHTHBEyb|!?`@o*sevdTrHJeop zwT0oAcEND0l*idnVa$A8P(K0ZVSeX`ivqs>8G5=X`&lYF5ee)Be(wuIckU$q*}<;@ z4r2#7nhUhaoUJcj*VC0s$-JYm=`HaJpLeRxTzn;J_aSv6KyL2}I@N-Vcnp-x5iQOX zh|qORY8E5lSTmQTC|@~e(_QfIL@S-9IHiq1PS)wZ*$t!IY(~`< z@a6PU3WzmFyeT?es(00UuAHM@*;!`}3SHx%=v)j#UpfM9*n2$NSKt9wR?y-h;`3^0 zlYNOTiCjHHknv2F8#vP^LJ`;lRH+t>(JB&-@R!sXn&Y*hje6bmXmdd%}w>*#3>A))z4~D%XF*+~}&sYg%I=ANO zz+0?E;B}3LCnPO}qgGQ!*}YM8HpXcy0t)~RdNRI{N?XQk$esPOG6h--f1AR(K2Yziif%z`E-CQd|Vjt8W*X++>o7Rd;B-rq6B<{d^Zlfz}sJqYrNd!pa_ zv~xQf91*{23mLP% z=BlE92usq)WUw6&Ro)nNR3PVL#>GlTLTK{`kJK^8KKJLHq&ZVA4;v&*36q<~QinCH z8E8{4&WTw=(-taC8{*&Y)m>{mW;<|X=qQp<-?&t`l^B*7m*i@fXMII|Q+)w_3;ssi z%qnt_Hr$~Zm1?=m@E-RRyV`{IWmoBEdvGCKTzT8TS91N#R<1Np$x??E36qMGdv<18 z-6C$)sM&E&c*s)~p)A_WQ4HKo+H)oAY8H!rC62qL1M);9P+;YW0|eykR*VC;U+M$b ztVo>Ecpx6C5U+sWXwHg;;i@n-q2H3Oeh+`um{bho(vHgJ^=3xK-bvtgD!Q+M%U>PP zQpY9F=}<8`)-ouvWJa~Y#!7b;#NGKhR^V@_k;Io-OE|z-BG$LdgV;o>~$$`2S05D;l@z?Bzz6w^+;vkT0VL`Ae&SJ zB7L8(p|q!#^NJ=dXA143B}42VU%KTfd%-Y_rKfmqA9`_DiO*O)Ij*dIQDvIVs0itZ>oVwYF~0%fjhehYKuIl;r$d0Z{9rb$9%=i zll)UXq1#cW|ECVFNqkfDd4YUbD+D05 zKJhAu2Ew|aPfc~ZCwAyQQIaVTo!aw5f0++2`+ zfh+wx1C4~2ezj|#t5caIHkncw<$=cm+JOvG0#m%$7+%6#0!l(uf>y#n0%Jl&f=7Z$ zLQ4YeM6o70Tq0?r$v#Hbi&S>oK*JS54wtBrT`Vs1WpP4tXE5gz9&el z<)-MSY1?K(>7M;TV#DV1BQd6`oqLQz>u%LYpC1Rvxm6ceTY_XuJ75~{Ri=3s%%yL4 z6#hikAX3@&grZH&61yjBtJqUC;@0^)_q%a0ZOcqWj3q!fZc&6{W!}EwL@8JOWf7;1 zoQZNbbVuXgqUc6R3poRBwF2_1*5G{UT9_g>pDmxZ=^WXsVIr-I@^#YnJ7jA-{r=6I&hH zN#!;#6L&mW<`MItoSS0tjqbmAvUogwxJflVDmDxZ*!0wKp7%)JmTY3p!_` zuHK_rDjtS~%J(<3mhcsP630pGaY|{xrTNUfkyAR2e)g|4d9Cps5uy_j7CP@6?Ks@& zD@oo9BS^C+ub8IcqJ0ttGfTxPO*MC3*);KI7SZWza^_vsPrlMgp+5&xU}>sG!wO{^ zR|1U!mknKuS7M8-wzvmTE^0?UT`PZ#$+IFUc4!P(5pCp z7b^|QjLrMQ$J5ibz-r3ga%PbOV#S%pE>P3v!h1SancBz>cSRYh9a=?~s;+s)!5DC* zhs}NNBxPb9{(sAtkPxmn)jm0+ne-N z2lo(C_W<2mr`PV|o*5!yugWoq57fBC^<~`xOZF1oV+Rm#!ZGsuSX|=0F%UyrA$%G| zty?ztS=*)7-2(-Vb5h7{7p#o(s;ls{VtRUJRB1_!?*J5fg}XrBY(FT1<1q@kF3-Y^ zhnto$jkY<0=g>?wnXk=`bXj66^8t?xUgLvG)2^uBq_m?G_vxMFH=`a4q-<@Kqbmp| zB>9l;CEI=+e-Y0nbj@oJ-|5m&y!eb})kCwC1|#U3#rTIz7s+a~y&WitVNrTy^J0QP zwIFd`$;0bb+`Qs*0EC3WQS1V8ibwY_8okmt%#-<84>$><$U7m0&Sf-WAIODLRZMEX z6z4JIJ>naiAf+1$V0b5GQ)-z#?pw6t_le&)} zV-DC~dpZj<`;$9K@y1FXhCI1<#^4?rl&@3QgD*^iA64x0!*B$+-7#UBWae z8y+5zDNDMW@1WS~!l&nI3&`zv23(b{R@kq!TJ?G{OPeS2z68QOa^h?zb6Fm#g5F+o z)565l!C0(>i90JJxK{xo!7Z9YB%l;G^8e{zs}KkH=E%>ead@Px{N;^xTF(Aih(%-(+? zaga~hD5!tGa;2Ed?Y7$VXPHjdNo>w;!jS;vL-J0eGAf_jEREX|t+DS-aJAM>a5*}7 znxOS_w%Y_v2!zBtliWNgr))mBt4GFNwi!;Gh3WME*}6}k3xFV`x< zLD6p(sai1gKU<~W5+)pyia28fSaQrTgkHOh4BzM%63Nh#v#v?$&}`kf48&L3fT`n} zq#E?+Nb_Xm?Xz(|{OZrxw>rH#%R1G<7`Fc2_ev)>5@uLnxCqhCGGIhAxt`=o za^rrmYEHK@DluA_x=!V0@^BC3fAe}SyPQ~?ad?~UXb`nlw!Yfj+{|txbSMd7OU!U^ z31UYoXj2)e46Auaq&@O5RqM+HH=mYQ{FHa^371(K-{zS5*J4HcUZbAtFDM_a62_-6 zhtjg78Cbj7yhMLTeqNnor!6X?j?v`G^whuBA<@G&WVQfbwss6WNV-0pTo@PYS(Z53 zCa2LF9}m@0K*EJ7gjNp06~1p~Dy68fV_%EYSZFn8Gv{>>FAAwXWTt18!lvP?EY%Dj zJ{}%)BNQKEpm@w2jH8EjF{LIST~-emATQdZTNhm$@1yqG(mxH9+IGf>Oayn;ho zgr3_1dOlpex`UYIRWQ*kUV$b(>T*L78OOW=L{D2zt8r#2)vTRS+NJPn4!cD2l=Qm> zCDT3vdEa6wLRLjfiTICBfIoE$nOu4he>^|toeqZ@MbCguI=8ItwBIdT)m|eG?Oi6W z`WU%V4M`Q~4ttQ(q8WLKZu z)AEbW>s2UiCgjd}(H4BydS_(kb;>oqjG*>GE|Maax~k(xvc8e}G4&zh&cjs3^pD#^ z@PkjZ^}lIv7cOrzZHM!QMzVVPn}?c1-aE(K4e)59b(9Ah2J^b*sf$s;f?FSaq%4I8 z3a%*hEijojCk&wi*oT_EGG22(GR*KWRjiK#{>^|Cm^6fj&b4K1D;idpG`RPFgi!&PcXzh}kwqAiwc$otwH-YVRm!q#YQJ%P&Lnt={ZWph5NFkx&SH>mQ z9R0T#;KyrtihYj6#PX~5KB7cR z=?sG$Sp{=PnlU!0s;KO#GxD8*}K%1W8<)k#|ooe|xCu5dRvXaU1MaI1r2So1D)!R|?Qa!}` zxlhNyu~9KGrfH1xF|+c>b%|O~;B%B!EPI|KN`=_4Qc1Yp1==k*xOyE&NUkN5mlY&V zzh$6;NIedWNI<4KD%EZtUn4p+(tYL5Kw7C7wed;|XI9emiYee@onsC2S%OA}siLnl z!S+<^Lf(0UMLl|=aC01W2;u=7WzJ>{ zCOnJCQjx|}GGWCScuq%(aeLgQ0<^m-b0x;3!Lpct?iI=ul-&Z|^fH?u+=054X>(WL zn>NGRNDmPHi=JT2!JkQy?1(1tP+uS`hCK5cv-^~R!vpy>lmEo-_Vuz76Pagjpc2=O z8S)vwxs()yw7TDz!{?|Dp;-&H5|;V?vO8#9Mcg_)`w?WlyUHCt9hN)hQxnLf=!?t< zE6X8qqtoFLWT?@4biJW>>KM-xl#~fL_k$Z$Q*^lA4g^YIGxaqaaP{?Q2aeO>(NjxFMOT>DrUj#tD|h-~DZ z+t(`cessRx)1Ncd?Y_c+#?C6f3c5ebY$1a!M_9Mxg6KNWaP;(PFG1zj?ea>=6H#A% zFd%fbE;F_1gl@k&tzMy(jZ(brs$XX}RmE7N_rRqzwf3;!xiT)Wm_%T1r=bt2Dbym9 zDkv@Hu6sKC06mUy>~J#@xR+c!LN+T@Ipx(Zh?Bx1*1&br5(;UX!y7!eZOmBYuvi_4 zF1nMcm?9z~krDCw_86JSPu>L|B5tq9rEZc^P_81~)Cze+Y+^AlYG9dB`W$e*2&=PS zdcWqCi6MNFa;yNWi9V9Ml9b2}G&kWnF_OKStk{z*H<%VY{{6boH(=8aCKLAm5gN*t zeu5{QWszDudu;9I2BP`!bZYO}%78#G&XA3M5hBZsU2TOta=alk=9kIC-U%ev>2H`G zwQAymG3vN3mLIz&l95`39l1cts_>&+Xb?X|T_F?aXBtD7DJ@;Tk+V+WEVo*k9bz@# z37+M5pP;60!T5spyVwhD2y$Zp;yl2OKub{etR6o}-ujDm#Pl(Wj_Q^%>Bss(C|aZN zw3!88I9;>;cFcK2df{w^$}td)k#l?(&dU3{XD8=5CPU2DxX@V`E3NNYYb#}EVJ~x@ z5%F0$6Hk=+Og3eL2M0XWQik1p^l}Q(_CHg06Bisv6n-YagwuLAE)BW&(~ zY8&0+G6Yx>fbN)UsVrPj7#AY2KhbRCo>7vGCXS2@b3AkIqk^e;nS@q`S&wWC?ZG76 za5BaVGco-O%-aAm#v6jtTvZ$Us+wURw`iH9r|-CXvcZlnDsbGcc zng6y^2tPHL_U$;kT_0(ghBIq8SGr^!hA-t~lnGd4ZR8zqWIYaN-d%=+kjtZ=gqku~ z{}H2TAxs9m!+!^fhaiBy84nqU;usmE9y}HW{8mwh4Fac^pji`U zeV7w>w55Iy9zV;rii7Xt!lbCS_IW>sXasYt)Z~YpA(fIcAIZMBHbnOIOTca63;grI zhq0SOY1>+-q?3B~b4i6+BDc2x$$gn8TF=Fkt3&5j7gU!>Kii|M@z7*;p4OM_@s}lG zB)3flH@%0&bJ1)*F66<~#<4WG14QyR84(F>t zJKwUP&Pz!#tg`QyL{BW zq&#q%U5FDtB7@T!?hqtgrN+X*skIAOv;b=zZBB-ER?C=Y+FCc$9q3kuEqD zyIEA-9LCD+IH1UYh}kwjYYs2HlzEG!6@F2rlGiKC|oLYe}fe zMNTJ;f{1#%58fpE1)P?&3(K7oMNPk%V$IYxgjyJXu-ppe86kDvmI2{o^ zEMV15dI-8`$+R`4U)P4($zoo{F4nC~b#OLQTC_sygyfj>?l!QleK$e;S!t1%o*pCm=VN~xwzT+le6Qq|bE&So zAnwtuG&1RkMDZIpDfRkHp;s@sqvGRYoB8iS8WqLEw$ag{l&qbKnH(O!3Wv({tZx(9 zrVG-Fh}u!&`2mB;R|cyvJM*)x;n=-!**cN9;ew-;rIoC(ay~fUia@`{U-Sr(Nxic6 zV4+!?uwHc#lnM|i?eH8~?ehpzOPxQ~^F!dn>jtnR*b@u`>)?i+dT9yg511ZXTEk_9 z4;OQX%m{^K1@_@IiEYsN>B0wl{fq0=P2>^sk}{+`-U#B(f+NcLDzb>uk_Q;oB4*q5 z1eXenJkr(JGeUp^6c$xV;wJ^ZfKBLwHTVp+oXD4D4RJu;*dSYZ?)zFP0)>jFI5ns; z`MbmMhaJ4&%i9DLOBwcR`xZ)8YlT&Eu?m#)tLu7|MMfTQffpqmvaz%=Y`E1ZO^%rf zB^|h)Yc6*YtO0R>N_*kNd54@5&QbqB`3$ zGxc6r%uWtB(G2a(H|=GJbi%E8e)UQG2OHe4oej(3FH{(QNe$gC#%85G^mpwV2{cP+ zWYoo??vPGz|NdOn#EZND+(h6v;igqoGHaFCcrOr>ot@3Mb}a!vi_BdWF}Z>YMev9U zdQFK-yTw$t1(V!_`xhBV_7KX6&dcoRv;lRCYQ?R*BMJiOkn1xm-CL>k90M(qla^>L z7u)BGp}ZzDI#zoEd^%Iy^W1JYEW5HEUUeEBDK59j?{Ai96-ITV6O&f@dg?dhrrJb_ zTLx0aWXe*63u#&Z*o<#=K-e>24OJ^3v<;@J{kGa-BI+k6_eO^snJVy+#?&bOB0Uva z9dt5nD|p`QbJK~8x!L52ZS*Ce0xJfQW@?;tRjzo!(FMyMW%b7I*fN3lC#Ubhqk!i zBY@}MCB;}M@2vF-Gbzjo@+>|td`#wFyuaZ`g+8nDD(5;Klt#;MxCbvCbRvj9Tjam2 zv*QNjKO<;Sm&Zv}doO!Y0diJcN(7VF$6@=f3p2mgmLp`=R1lNf5{9+09AGiB3xu z9U0v^z3hM7sJ^cA4#(nPq^z-3iW+7qAcJi{dw-%NMFosfx`@mT3=|0pEASo#k9K%S zs^G`yjm+Hfj+%+#otuh9U%s!RnH)HC1-QVZ;WqfD=`AyFWB^Zv9rHVMy%o6iN2aGt zbsQ`3@O2m6)J%SKDV-;)5IupQM`&6Imt+kvqQt~`(=Q^+Ha{P~u2SZnhT4k!EszM~ zy!Rmt6>-*?KinXOMO>r!dX`=j(ML);EE`t2RWKb=a}R+b)yBKq+eo7bDg)FJu2@Hd z)_C->k4dsxo^d_r(^h9b!bKN^(jh$2Me2wZAij(4l^ErF6_uF<8inX$N*KfrkZk1P zLC7}t*nyNWX=O*><2XZwFQ>bGC1P3x&A{h8HTGUYx_PbZMD9YiN(xmKlUbq)euF;T z!sNkeD-|>ry^R$@joo5C9RP`ou0mKW^eC!Z|~_q>TqxGE^JW` zgD68I9UUEgEdygOKmmNLuHHW&7--O+A4b14Nm*vmdPwMXfIvmiFIT|9Dd1Qt737dR zM%9guE0d{fMrRlOUke^q&}wr6zifDpRYpq(Sc?Ig|1=ubkW0Du(+?`6ilBHbKWGwx zm;_>CVb5MmqTydv!}7Y~-E1#`B9b+mQ74*cwvn_vVe~i6UTeT(&FO83$w?ZG~rF^Q=s^Y5r zZA6^(srpvF$0Oi7!B?<0wwNO3lF-2R4rjEG;UC(Z+`ts6B^elHE%U~6rI6B8xp-X{%|#>F;Up=Z|NP=H>|JzW4F>e)sM6)%MxX{!K$` zCRTLHsG?zPgXFvTJ72pVyBxb3yBNC`yA(T<52yIpDyOB`Ld56^{Xgw-{dT++eGsjP zO$6e-J4SRHfTF?7b0OD;A9=jo!8no7+|gJ4qU|X-QP%F9&1hhA9rYo*K<{kN%#wvQ z#-s+2UX+}`jAt8bYoiM;;jbOL*zZcu)?EK;^zgt8kv_1EXEWB?duZ1~f>V>$n+Cm2(X^CTUf`&zZu6m_X*tPSIlDwKta>5jV!(K-cNO-mK( z8L~#4y{Xms^Vm^In@bvwObEyw_9ZGvdOBu_Vt#gH39Np)bcy~ri?!-y3xHD#wnxxD zs_oAzD1UURp(=SZMuQR-$m1uKpV*y3ErRm}zu~L*s6cS@qHpt#Qx?;MG7BYySOmYf zS{S+umlE5fNuedLuB-JMrg)>hP1)ippzz47LK4;d~#PEl@t4jljp z0HBEy)ck8t1^o5p0=WWSx`ViGs5akrg;NjF58;zHBPHll#>KbSQBw+(iJv*jXJWY7 z{?G!SSzjD&O;b4uPfT9WFpf+_?%d$v(gZxDwrLwX?zE}cQ*oXdc+Z4Y7gkg_Omn~7 zqUg*1`TJ;YnNL6XS20YHz@C^uDBIyDjdAs|iJ;Y=&i*TT_Gj~F=8N~j8@fz%2xl{o z0Zq6xSF95pOaXP@vRieiGoK8M*LJTTjK-0=qPl#w_1|@D$q$JaZLnaV`H^~4s>y-e ziB?y?1Q&LWd*ARd6pMBKzjesZNtpQn1!Vb2d8OWILSPph4iZpD+d6b&y^4*i#f#!{ z%+@uFUNYdjR+xh?vH(a&u1JzoigdDjcBz$eX8S~tY_vbw74Y%3W@N#6T(zqWs8L0) zj-F$$ms4S$`|;-Jw?6K2$Y?q8>{oCh`**UdKJD{iL{NDUL(HbC}$2sXg*i=+26DI`coUniD8kh006JaS3WX zG>I1KO=J)9n;7OG`F*;NV2xfhKId~W-U|gWJxpJ(o76IGN5Sd*bL)?VW*hz|F+5G) zDBfo8b`R_0)Gd`%J6t?JB8OK1MpduT8KDZFQc32DV#6#bL0RbXt0X|W{&J*P|~e-Ycu^>GyjV)cXW`i`}0ND5j#f3 zB{DXVVO@R?N zj$H%A-%eL^S+Vj$U0q3K%vh$#p#$w&+Q~W340=zT2RXL_N!xA|Mn*G=Byt3?Y{r^4 zzgS7Al&~hIlbfd0pw>e7Rj2oQ5e;C};OARprmNX*{Wt$&WMJLV?}9N9Hg2IbJxp*! z-`t;vr2@T4Uh+nfMX-5flgtZL)ctDz$#Mv%9C0)2CyVdL2>=^!7 zY64g&U=d9NA|I)T5mu3Cn+w>s=oZN#**S!z|p-)!@HIMB|zQA_7&R z(TnGDn#je1v%^+~;b#&bSr$z{jg z3}Z41!#>bf;|OXnuA0mjqzC*>m+2@Rxt^>6txplh;xfM-8e4*qu}rFqLm4zDxx-Sz zk4}VRZ@XXCK4=6?U2hGY#g_c&FGA<8i zgQxYOh7}rb6K6v4tQ$(S8m+C=D=)ie&O;!L<`1LTAk5W%DRIU)YB7Ru;N=D*e#g3? zr0wPFxVXdUNN8JF1!NfuByZI-50{k;Z%hn1i;-wS5rRiQZ0-pZY-S~2MHeuUo2^Yj z^d{eJlG%yg@^H~rG?Q}9n6VRS8FY7lRy+i4OM{YRV1 zxLrT&@c=S^*TmW{Y8w%ar213h2Y_}c+udPyU@9egcHDC(_31ygMa>C=*6!iq`g3BI zGkFqj>4Xjd9Dwm7dsnJ_hZF)1fD4UbaqA!KO??S$$nU)~`3eei+s2NNgh;u~;fDyu zxa=N82tjSVlJw$)w6a?OQWo->7({>5Mp2&jJg1hg&tYRA>~VnKhQEPVa9uU+jEmVE z!e2)wLfPaj$;!)FNP`UJQ$Lq5?q5;gp@nr#%SdK{>7^t2DkTP!Pq1G_v;&-G5YQl> z&lqBBbWPKpZsUsUjB;jIpF5~zc|dHC)aEGnrSZ959e(>ki!31B%+N6HaeQB_VQJ$) zYWyQm&tA`Q9(?voO%4_o>cGe++e?Hm+a7`%0nzRSd(i}H$b}6EPTKQE@CFzYsRsbV zO<-u(8f;|SEwdkdm|(b)ycAz0jVCpk*#WZwrNni$LQj5I8i)u31kOC+)C8=_7SI8z zm{9S0IUlD+h2^)IkSo0gpDg!)LJ&*>h2)^n`=X;&F~=AnxpA{=&Cz%*(KXyhsG)Cg zJz<6bt!eF?Pi-9vE&=?=HY!IO>n-smT_c@)^f7J&b(>Oamr-k2eu`*EWXTbSRQ#ZM z7^ZfOn_=}~jWCz(e?mYp)zOn0mzR~b*2%O1>i{v-D19Oder!9v#p(bFlzyEx~NR(#3&6kQe7&=O>N#+a8#GMFS^dilnJn4 zi1c4$t8A)Fs0-6%6pW>|!n#jG?2|=n`QGwX1Q@=mW@?)1ZoW%rp`KM|mpwrvJcozr zjVBHB!GofNn7JM-@U@JB*%4p^{vgCUW-gL04|Wk+#fMF|o6lLgg?RdM5#y)h>7~Oo zP$QCwbfC36|2?-qV+sO{?LOw(9AKxw^Mz;2#?X`Bs@fF`70IW;616T3O;jHK>076j zgi&_!yl(I2n~bH&cZ2W(mPN{-$yUBujL``fI*dt`cA|*HYsITX?KB`V*qPrnP!lzg z$BVLIXfd(cK2cr&5D`v}`}zoO>uulmg|$4vd^@&}pyu}>_tCiUo7UUn$U|8PxA_cQ zxl&mqo;Hd67$J&_-A3^G32blFA%Smy9#3&Zs}vc-6mH@A;dt#oJTf0d$U0tefBUi( ze2n^uX_YzV)8BSUNT2{14~iMUsNVt7BU@$>my~q`!`vTqIr4#?RAWKE5Xp34odH0= z!2ve8S}kaCX;%!mf!EYJ`kB>L>;Ze+);l+JRB7ysO3!YJXV)w&QI zg}xroV1rIv;V0Kl16=!P5N^I?y;?92q`hxuB;Bud3M|+{Ni{u@&7bo-FzSn)l zY~`^@>=K}BBQ;}Q+#XZu4(=Fn`)2m+u)!k-G_>)UdJ*78UUl(<>*P2>@BVZQV5hAo zWdV$`;yyP3TZ3{RTFtno>T&DA(sXUt+4TmfK_BXYdXVNN5I_(bXG|D1LSh^9VT;y| zCpA&nrqT^h!G~aZWlz}4#k;5_=GaNjYLL@SqR-NUh5~Zl{)Hw@HTgsK$Y98DgS&r# z7rj>}&o-u{u_3iYVfUxYv{`wdIo8er;YDxyMH zVX!28fL8)SiwiLX+HepTd@VBLGF7d<_zh#^tukHsh1-u2Ye?|!@S~rvvlbOZm;8p7 z_!SdfyIusPt5*6}RMk=Ui-?i*|lhrKy2hiCCH} z{a@(TFv_2pG+_@}jHS$RHm6yAp=!JK!LfKU&a9(#Q(Y>cnBTL=nW-^ZO0c1BH6%jK zZw3{1(BHzM5B(T|nmeLVO=*Y=+nWa>q&%LQN!wKMn0Vf5)FMS|o;K+Yr5zQ#$P5 zFg~G|Y?1Fk+3ZAhIV;!-LmP_7*dU&ibWyQ9Uk-$m(!wHBRdOY90tYPT8hK;Z@ca6@ zJ1{})hP<-4q?DDag~ja-ab^K@&~kA(pdz!`Fryzo(ZD{WdNj$ZHfJBtiiN@UrPkny zJ6cCDpFD|>U-B`ilxv1+2wOV;0vXgig#$y$gQ3>PoVA+oXIybK!Q@rU3#xoj3<)7B zOgDj;Q^M!^@b;zl1c4;sl!>DJTnlnw3*$fQ+6Vm<&Pzn_C^Jdb57e?<=#d0m6E15i z9iK1zIz@_Sma~f2t31w|4#q}!F53sc-JfDx&3kc%DeNK8@?!QTFp4@t$~g*>Hd$au z_?_Z=aec1!ZeVe^8ChBqD6XmTsXTxg#>5tIruKxle$imQ2u6155Gkkv?^5x8<%CgQ zWRml$ff*laDKm9|_n!oQ5uNe&)qFLesnj~~u@dmO3tchZ6szr|t(^UX`cNRK3<<&qNnWx&VOqIInKK3wkQr+F@BM>gLl1 z=JIi4g7!8DJ42l?txuQp1oU3_8dFjh`ksh5Sr=A#D)oO*y$>~nyptk=jLuS^RubVP zk!Sv+0+0muLTV=LWyJ!ND~@u8?3-?fX7wue?;2mEnItj1YUxvo&)fhviuaF2Eh*x$JdD-csIjW~)&=oKD=Y@5D zzWA(k@|86e<`*}GkT9?1StV&jCI6!vG@n`co_ z?y3XSG8TvQcKAHIG`4%nm|6R};Ry3Wmk=OT(ciG+uh$H!}vG-N{$SsUD>zWAl!;I-|wfQ|y-z)@~rFB28`08RtSLizn}dG1lpvbu(MM4b2fdt0Vj zMn~rDo_`bcozzlB&xZ|vzol?Ps>$i)s}&HsCRyxp*0ZfjP7MMG$XoT$dCzR!Rad(iGWZZ|i7E3C%M_4yu=Y2%y zDD6U}$xYoHzk+*+qZwr=!lY$84wBMXv5FKJC98E}ZX|&~z6&WS1_3aNa6X|};8wx& z4Amf)I!IiBKA0vDf)cV*@kH0G0{A!_=D+18Xfas>fspz;a!CHr?>!(w$Q`|@xyo33 zumRun9>55_n0bAxa{?lGnHkyH8Q%33*6KG_EDZ{0kBZMP#bW~+o6-4ThIFBV7Bo1c z`T011(VUflrkCOCzsx#3(^>-L?FEoATY{eo6yJ4-b!?rbcVUuPPb)9_MMN5l98cuO zP9Q$(@MR4^4BYsL)A|K{a(32OCjn%{MMXYx*X`|Ptxz)^tPZ(TsrrEX%R(^Jtx`&sZFOlrsKxnJH{TUwey9>m{ysJ@I z{AAACnmx3%Ji__ZCkPP`Pr!+35kncGdc#)#c;O&v0^LCIPwP5+0Zt}p6>unz?V|(g z)WFOvv8;bnzdBHBU% zNlF%UbQ7$ia7qQiBkDCK^1Kb|E4p5#9oE^{msLot;F90$9oLBIq4aptx-FA+9b3S0 zC#Y16$RCtdL>$d8Oso{ThTSH{)~N^%Nws5ffvoRZHX%bq!y6d?q45$wYRCdu(ya?SFth-rGjSg|D)B0Xn((j%D-ITWgS-J z1U^4K7Z~4)B$n~r-z#4P3;o{S3#RAUWaQh+V?X^~Ir*;_Cy>1=jm|NT%IE;V7BNUB z2QYP_Ban0ebb2ZDuf-8b5@{=K_pb7IBlRZifea|`Q}`Jvp3d!&`K7BC7CLGnQ@-xj z3z;mxu_WQLySW6%KrQMwjL0}jj z3K;?a9Z1D*$6XrJr;udlV`S#;T1>GF;sqik*6a&xSQjQjp@}DvMrt2UFTY_qef7cv zU^;Hkn5|YPH1Q>P1WlMcTuxuNu#nDBtK@v+;ABV;RTUiH)6Y$u?{l7-hzv3b+}PS8 zdQ2PJw(+>>Pz|~-MYb)svsOcIG-y5L!9+jlg7!ZUCD^H^wdnUHqGXp~9a*G~)cMp; zpdaI6%QV0vfkQIP?JL}>H>Gk}Y7(g6W1HZVoSR)Ox2uL&7&e*>l_W=47?@pNrN8!Y ze2h>NB-lcnU8S9M{0r-xXUl@kMM`^|tAKIB4_{H$m4!lWx(Nf~Af1sKV2_8_O zsH`amIy8j3wr-lm5)_$Bh;ib9E)ogl*tK5tLt_FHpotu)A}3Stj43O@qpO{cO7=HR z-mLS`)=k{)C%cA<>#7k+zNY^OTKX-DgN=hIM*~gouk5gnIjgK+ftt_7lCe7`CL{jy z6O)q@g*~(HAEF5J*}&vvAUo+_gF(=QvqCm2d~B39+mG|O<49~0<#(4_uRu5Ob$Y7G zSak_8R^xF#8a*&KC(O*4B#*!slP-z=3}1~2iKzp{MnTA&oF+V2+2(i#-F#)9GyRn% z*#s-eENNko4yKS}Wf^vbG`UE&hQu0aD`j4!?p6eYIkHH_d?JxgK1K8}JmZ-TdA(k& zGGo}|4W$_`&rD5`2i{bW^S}ev>kUma9-a|*u4nHOl^{0eVG3l|Bjxqr6yx(T-dT?) zB1E>ky`&d=W<5;AU0Wg*a$r2{xsz~sw}Nm-F-@i3CAE{mP60+BX8Z9%@9Ve@eYBoO zYI{^0G=TgjVbuZef(LHx(cB7vHhNe4Opwz~fSY$Unvgz+w<21zi0K%)tOL?8%& z>}Cc*aE3FSo*X#4lNOlS*&uG#5-aVjw6l4oR@@}{Buf~Dv!vDflnBdtC1=5sqt>!d zI)Tpjt%Iz);hp94|JLdAVgB#E>IRA+Ig;-r`#us~9nh$%uCDOn?+ttCb)r0ap4F1t z{<*pR+3ZP8b~znmd-u=jC+4S7JtOPOC%}UL?>ZB&C0HWS_-&WWp!=xI<6^rKi3B{2 zAeG{hvOA5A2;*m+l2qtzkESeKC zQ%a@#RlRtn*pP}SXr%mKIemJv_l>)s&_Qxr#|EnVImHo$T>qFT!zB8S6y|~4KuZ-n z-$Ir_$HwwtRl_2jFqc$@W`+}QWS@%eZafWT^d#9YhaMR&Ib_Er=J$vD7X7tR-*Egd z8@EJv>o67qzGUNS*!M`{)C6M>4uF(XmqghJ$x{m4r$RPjFFgtpkqWy34nRgyv8>cS z$v#PQXc+G1Ci|(pwO5Eg!FO1^@YLR$m!A8|o=-d!9gRc-!6+Mh>cY~^FMs8^hd%LV zfoNnj8s(A}lK6B%Teg&DAQd(>6FwW5nC(6j>FZc!vT_McI?a|H$_AXnr`|5JY+8B- zHs@$_*;Y<(Aj?xLldEKR+Ge*J-NwsEX(mmGQ80fJ$h8|{H^ArQ?bMvLV9%T1+!Op6xMY8r&Pxt_ z{__E88@p&&|Iut@o!zH|;lQu%&;=E)j zm?yhkV8dqThFeCFe6KQepb52Xdbx7~Cox#XsOX7M=-q# z(1?)Llq>pj=nLVIaCqd~l=>V0pj7PdVE(blz( zlUtVA@;JI#PG|`kmQ2HdS<>{;_oA9EFfb61gb|9KLnIji!W*~(cL5xS*e_&HXMuX3 z^)$@?cKW}aW~+D(r~R+OX;W52Z>*nYRoUGV{1;$tWztXnH{N%j zi(XGX?0e`T?kz@o1Y7=DKnW($$f(#fnbd%<8fK-mp=lMpuIs#S86?5&usofhnLr|+ zd+dt$F%537YZX?8uLRp%iJ|2U$OR>kTd^Xn8l^R?|6c3qz0zUo^#u=dxLHuE5f4k; z5W1%Db5u!rEJnL9>4J3+-E0_i?2+=z@`QGM?T3!!WE0wnG zDizqqyQ0kxc6EJy)6#TMlNi_FS~?l9#vu!v`s*L+zv1JR3Nw1&cFP;iS1LALMEBv- z+IPyb3Mo^pAAs6U_!V-4@LO@^vsYs!WYsmGf=y614_RoPAwSTr51>W)B_IrL^@sZU zLM#EN@M+71I7Ts-&3={jCrKDmEjC>~p)Pgq2TeMmU&s|_74k44y}}4s3ygz} z_`I|mc!dLC%eM?Iq~xeaJFTq%Tb3UOJ$OK0!eoqJDrmL@j){C$P=~y$})T;26iQh28gnQSSr0Wgtj|J&932v>DgBCO43$%EETVX@% zclut3uh$?e;^#T#@5XsEozA;;W;EcjVS&;sHEHMBRe|an+)lq?n$5}8$=7Y7zB~Df zkdx84ONHeSe#WHH)3*i3?@8P<9{egv7|e2JYGY&SqDHl;vj4{#H?t%sgeejf{lF7+ z9e-Gz_20a(G<{?3{>;=RQyJ_MLqi>iPceU z_%Yci7DI*sjUli|rLg}pNDK^vb!r-LGg`#I0oNgkXq%)}eksfOX9X5TC5aB>n5S!V zL2!oOAvYcvxF!t*pw3gnT!uyZD2;)>b5c$ywl53*HLn!=?m39=HOIiurYQK#>*c@)F3qdq@c1UQ{QUAeaJYWPt+MJ36}e z)?1%Y?nM6ePUSz0onhWHW4GS=_)GlCOOo66RwSRk4zfTZD;9a1{HW){vaL;S&bO@L z3x~g3w-iu^t6c8OHNFlQwISlePy%J;ts-fn(y$sGeTgl^W^To--&@m^C-%pNpBf$e z&yC-T&D`=5UhFummml9BOG!fAc^gEf_MR6#v?9?XT{BqtYCHZyiuJ3Q8V z=(!_D?ml|-Zl3;HI9#pOv^Vh!l>YpUH%em8a1<9UHuwybZY$wW$pbL4iniiR7mHv; za{BwxW&G|bp&%TCV*Q)*vwKs{iu#I`EB_g#Cgs-8Pbn31BYq}Le3#mm7n4x)P;JZV zH^q!>-s78O*A4j;RGWiUh}jKP!A)~n zStB{WX2kBiGj{Ncv4aO=cQ&qC7t0z^Uq$TFH+XsJ4ow|G;zdt8_K?hFi*U<08a=&}2JC?RnIh&s> zOj>#}D*&wmuGeB21vi!|x9kddne3LY$Ima#{%sU}Jtqo0XHS})8y|P~CA!Wp#iEIL z8ZJNo^|4v#ue+n@^_lkYdK4z^*0Mv1Xl&_xSEA4Te{Y?B@NYs~pX?q^5;Ylo{RveE z_F33)T`B@EN(432OGWInfRVJu)*Adou&i;Q^n)?5f@NzuL(B=UG|&Elq*Ju|O&78t zWMn_fUVfP!dc5&CQ`xJpvYU!Ukpcy84YHsjzfbZyQ9_E1VudcC+i16#3ANJJj1cf0 zp|Jl-V@=czaZ@4i=9u<{aTJDq)1Y#zlUC6bIY-GO;Gg(ObD5Q%b@eUwgfs4nh8&~K%`j(k^s6CCh1k6*r zicF{LmUQn=*q=20C5TPQVnWgicGu&N-&Vcxu`2wrKY1MXkKI_kt?{STs^k)o9)`#_ zo@5=^k>pL!DC*Z}0Oy#N`5YK1eP3 zA<8yrGN%MJ!lDgBRGQgd#;;zthMTM$&a_vJn?0DKlDM{g?Wk=O_D>Fp+9pd#W!Ehk zWa98eHWvz|EwdR0Y!?a4Q5gdZ9J}|p5(`m%0OAIBjn@Xx^xXXcZ^Cn!UFz(7wj0%V*nI)q=cXYX3P<2`WiGo77Gg5N&d z2|pWu>~9~Rib4Gu)cBf1BL50}0;$lfp$hX>fwfgrM*IOamC3v~WL4_W*Pp#6J^OLS zc-0!$X#c+E*Yi||Ju87{ne^-@8rOIg7^8jE`ciUn3UnvC4^avWJejF0@Q+SGBz0wP zWyKQxwFaSNZt|E2koI|-0UzLmOpXiZNkrZ57ytlN$pM!#IjFf9w(Tm{bBkKV#zrO* z9&zaDC|D%6&141U*J&DSl*HMItf}x@)I3(VM(5id7#UqR9wBTi3wX?{(Fz7 zI}}cgWG5ykvLlIbsN3Ti_w-HdeI91HlDE6tTgD_d8GmKrb~f*Jb@ccETg>h5?CSOP zbhz9Lj=eV|kaNB*k|Yq zAi{;Tq~Qtj=tik@1=AWGLaW{@WoVuoZ(;+b#Py4s368kM5@byl8?a+WQ3>}Ok?3eN zVt{wmU}iAP1s)3Owfn>Sdjmk){+xy??|7ze`rjeobrwjO@#V~B=h6?^0()-jsH|ZT7)(8pd=v|q~KVAJt2@lk9Whd z+g6KMD*<`h;3gagtbG}4Qq>uO{50120c@H{TV2z26Sf-c$h}v`14!4&C8kb(SKP0P z4oHzg?3E-b|AJ>ZDlLOY$2n{@Qu@&5v~bDrIA@*PN};T9EN;1N?qLR2lW1st4HNpS z^V(ZqY1VaCfqUpVc#}|K>3&M|%xiS9NT>W3{_yk-%>}q{IPj<&*B*ouYw7o88Ms%6 z)R5ROXs0#O@gH74yz^Y@Iu;H(#J0!8coZmWN|M z?BU5x-bSbvLv6l^4+SZ{@FJvS*Kg~~Oll@NW6egO-DROre0luoP80Xn04LxrkUty%>#fT{xg5~Nh;3a_CFU&9CM#^^iKs%+h^Dg6D* z+T8A`DsM+>bH8;B>xQ^(^e#l*rf@FXJyWwgAsjVK`&6_4>>f#7td4z=o(OhaiO4%% zgMUv?ZQmowJ3NmRu=)dDJwhM11^5&&aiCWVhviu&& zD?AC(^|n4NNpG5TxBisfPi3n{xmF)+n5~Hvh7R>XtceNPH)lxx_b(sYs@+;vi!i8- zyRF6Kw$`IoYxOgY=5meK)3mBtZ=3%%_{=9YyAY#xEZQwsgztq3kIw$(PeUW!t|cGg zyhW`M!|;3IX>xSjHfro~L#<6BlIBI>NvNvLxeA}WId<%a5O3UmB@ZASO6!p2=LyFK z9gM(h;wvi-Aa_S9fPdfg}7 zu3jdSAT!EqyNZ#<$Yf8lD!1&k<>iDgNJnaj=wClFi7e664|oCw(zFYc6T=^R_sGo4 zK>ivv18v`xx#20M&mOZe@~UJV4$eK)lYIveIw`aG9%|#zi8gn0H z731{y$R3xw@k;dZ8=w3jNIis=xQCEC_*#rL;`}QpI=CZFihJG^vV3W-=-^|ZbT+>A zwfo-F*?GCM+t>L>XXhJpaag9irUsFJ^<{h$_nz*IbXm<%2>qcYb7?>F^M0cg9^2>uqneP1J?jHRpdtc+Xq6>-T{P6tIPxN;G+;ZRilQtE> zYPLN{0MXq7gzkp+AYZ#T2Y9~I>bnP~FH@DJXLdE}hG7&X$nsgKe;m?94vnBdY2c9J_0e8S&8FE}VFHoPo41G8$ihHTbGQNc^ZigLfG3PXcW z?hjm`I;Z%K>6&3`8@d4mSjjX?xRE@Syr5{VAZmbU4jA2j_%~|kU8k%XWhNP5=TmNlx;x8es!h zk$0_9r~vd~E+OL!aFCLtDPf~L3Q0n{Eo{!Civ10Y(kTyIfhro9#|e3m=QNk7@jT{5 zz8Cf+J^kwHa(;Yi99Xg<=oYJSU5{6*c|KB#_DEq$3gysA>?O>stgcqBNiP8Ur%^5& zx`|ddZDTdM8Ba=-s&y+_VsZ>o%ZW%^^6eysnHjvzH_A^6h#XW)oSx?6D^AB13b_8#hKC#&S zN8KN%A^Z+Xe@d{hd0{M>yh9k}|4Fp8vF*=Dt{&xREJ@^9a&3)FJ{mx8lfU6rU1>R6 zDEeBcTn1gGxv8~bnk<*4e?4npyU!3_msF6GAXXRZkCVg8Cz!T!Vv|?Mt1IS8o}Xa) zzmGK{`i5`D(5Q>J8C3x;x5%~0>?6#vzf%{)URAI&2^pTP?&$1 zK}hpB_F!YCj=tv-#T;p&^3BqCaWOF<+H&L3v-~tNt)-c6KLe<}uQBtSlgS5_a9{68F#F@VkuGOnU(cN`Z(?{RAB+E&`H{XJufw71 z%+37$djlS)+&eV;*hI+VML8~WvTijEcyNPbE!;qECrL9uk#cx|`^)=KW6IP{PkvF=2|f1~Xo%v5skbc|=_bKP=HtfX{4}M{m-$6SR9dOtcme zNs#VbNKwW~RyT}k8bja0>`bP>R14P-CK}g5R02R9&O@%BgE|DIVNQ#Qg1`d21@feC zi2~om3el-R(nyYj6mU(jbFh*kEBJ!C|iHW+lTOO-|i- zLKo>v;*I`tVKBYin>rplHoRg<4%T7gcFg8FPyXiY8?;*ODoJN__#QqwzoTf~L0;?2 zlFnXk&hdnCt;%WG3Ksu^O~_U!ViS$8#3o{I)-+tLP4@6aY;rO-5jPE(xQx|RuFZLc z)mdJO+HZ6?oASVB`|_%}dED5GD9Ih^Ug|yu+lY9=@}L+>z@N2~+FKcGg)}`dV%W|b z(9Aq?Pno@9(-}6pWY(fH*egIGtg}$rC^Mupj4}}#qPAxk{q@saR?KUfK`E|>My$f0 zBm|m?W*CXs!HWygfeDA^Sll&~zIm5An0IN;gS#G~MdU5r^Ly2vXm456`6=2aXp zFQbI~#g{rdzKFx-)%f^${FPT`e$5uK>k0_#(JxzKP1~M+@=D+&A~8$oh7n>P8{55a zys?pAJ}|AEoY;MVY0kac_`c=*%yD;i`ncGN{ZgdK56*E{4ystQ)mBL7I-813$WAm4 zbn-wP@Um06^dJLcLOULZ;796~2DlA&R!(oNU;VwY2ghTqzpa*)_r~5h9y_tAszRO~ z^4_6gr53h%=(15V%I#0S0gTMr<{WK3P?aQ|I=o5iRWP(>v8=z`ExWH&N&xQoR2tvZ ze{B2>nzHEslwUrUW5Z*+C*sLWByngat|qcm(B3*KLi*5(MO)6#op9(-g+e0UpNV9; zW)5}7!^g$e;u>6wTHr5%S81EJW0gpTiW*(&>czUSp|(ec*gsgvbQ z{Owv(M_RS?ruOCp^1afYCtszvS+}^kfre|fsc(RzjJfUI1yb7k#cN_Q>{lUv2qT z7Uvc@AeABJUI_(MH4v&s&?o+)Sd38LE@`OU8+dE}gwI)O;XR@#lZ?Nsf_h+Y}&M6#%hz24-$~Q+;YeaXQt6nU4iux3AQ!P;FDG z6|7Ntecwtjb;YWe*xQ|?wMOz}8=rPq{n4A1S)Bk$9i8{Uk$m?D); zY76pWMO)K25&{|e5LaXX)1=cHYP&JA<<}-%O<59g;B%5h@TVs=rpV`#axFu!YFA(hZB}#i_bti zansT%JMGv^TTRl5Tr92;m={mL&KCW#$wz;2t z@lpoBUBE!FXhbq>1*qxuF6z}+=^e$Fp?;=mV z0^adO`tgraN@aWz$|%zJSt^5m`bA2GcrRY^j8b_awZ=D2;teO6qTPT8H#B1eJxBT@ zqW`mWvk7HjSus=BzeWdAw}sGBYocp&&WCdY8q8`-XbGDu{GYrIskml*w>P4cuG$hA zt~9IAfi7G$gt>|+P-=}%8Y5P7BvJkKOS~Oen3YX_Xrub@SYtjOTZx*ufKIxglK5G= zukm#@g#x2Lr!%dIYghZ3Go-dk2AJy|6XfFmE&lnNy^Wk#I+xzDCrG& z4xDvha>k&$!Y^_BrCPSdPO1%md+jyi@n5e%y*LnAt8QgN7htigR~s8xIRa&%L~;mq z42w^j-<)}>{dqBZVZE`T>x%HiqD;}&*dwk~bB=Gy7cuwdB*g_^w9(uz=Pi)X@;W)z zg#9FY^oKW}RJEd6SzkA|`HD`+gx@rqa*F>7_45%Ohk+xU`6TIg(7htHapnAZhQau1 z`_5ls|MheGR~r8hMgzTvJ?LH8FF6IfSXolJRqS>?VeHbY|Gq?BX$=#T=?#3T3})5_ zU16n2M&kMLb%`XelwZ@Qx;@Wg?HoxJA3-*#iV5Xg!*v#0>^q7BQ@6v>208)Z4e7%gc>XQy_u1hjqfKj7sY_Y4?E|mEi-|Vem3C}py?#osYZy0T2m2MENfn2r< zd7(KTOy%?Q=s>72srJURXWv*`JnOAM?<|=&e;^qAz|CgmOM&|j{?dUbBuQ>c%*C}l zEyTDI_9XWY*rZs2I9e1Fkr|f>ZN<1`9Rs0(dJeuZi}Xk4Cq~mYIQ;!V!*dC^rM-kt zzr`;sKs+j*wEI&270vR&3;RHFP1ydB?Zsws79!)j_Tl$TS5nzB$gkG()h#eDfg9+6~QmN~O@c;(2(^x?zPxWO@#tb+~v zi_O^e^z1vthp4qXg;loo10zWz%(vvF5P%*UZtQ>+t1T;&nmcdV-;#MMD;Fu!Tq!UB{dXWxE$_d0aeujZNKTN~ ztdfuqaXtldVn%b!^BA6dBWr0^1Q<5>tgd2&{hDo8h8i-lk40h36}DeP?2cbRt7)t% z*-dBd@xhmtT5;9e)8jSKEc{V=do!C)p6 z7#a*@fZWq<`GiZreng57sw=f&O=bm|Mf*y?ei$|E{RgNX+)JG)V*CZtz@Mcw%;O$Z zh$E!rUpa>D7Q`>fa$wq`mo#W5TM@neBQ*DIY*InmSeKMzg!>@NvZ`)}b3JT<5{JpGZY>dnRnuAB`v0GwW zZ1?lh>!kan2PMh2#ZYH44p@G!y`9|rdh`1%Y&kf#?b_{gx&1zC-;N#6hLNW34s~{R z-7B`e0T;Sp%R?HVTky&9@yV-P$GXmySy}z)W?UbPu$Z^&FYDy*dm{5VTtYt##aX zEA8+LB%&QctB89R<4-B11~v_BjaRtQC>;J6aV@tA_A$%MB=SfVkm<5bM6%XZm1onxL({d4 z5%P1hN|s(rj#3%rl>FY59j+iB3LT)PT7~AgVxKUWYX2)W{0mWb%iw8-Edep?_Bi@| z-GRQYJq#PA!}BRz~|9dEO zqWP9;!hrmQ@HSPt^*OtPG@#@P-2STg+f_Qc396=S`MqH4Aw+G{X>R;1O|-P?aL%Ti zGzz3`rBGb+^_!o5`sUr!GrM-pOtU)NJUDpQ!*>l1(h8)r%67l0U3mKG3&XJk=gu97 z(Qi6}5B<atzKg8^uxuwxYqs{LE+Ef#k`1z_0H=V^Z3W z=cIjW+WmwiiCk^T^v5-8spiqii~WMf^QFZvfdx?GKf{Pk%_V!I>|=0>7d_v~L{hUl zbY{sT^hY18AYm!S(S+v-t|Oa+i5WDA=srhUTd+a~m8Q&P4c~CxsNA@CQu*TVotiwD zc;H1B`?PD}UeCYB)BowfZ^F~^v#DpME6@0kUi-zsz`0S__Wop-0_Ue3&rG{*4Iq^t z6(xd!oVvw|%w|r%N!+h)W)HO_xrb7t3!|e870&rGP2>!J6TcZHzFT4yhs2RBNI$I* z50cL}HBNF~)DPKKb4dPIAjA-sbj1Ms4g-&#BK&ROHR`WokfB#~>rJAw0e_2C9^>Y( z$VbvH-AibI60@E(RM??#Gzy05V;SM6H&Mp2Vw>%DGll8@xtH5|=7 z`JrsWGs48ecVkt{tOj?bwY7+!w8J6t$OKjc{Sj)LKTK)VNaO$tM6#MyB7)^TM>j~} z8%S?~G>~l+1KC#aG*^xaA=3lTRIJkx9)FCZi_m3O#H+eaC-oxUQ{nI;9+841sfQ-z zwqlv7-$QM9lq4?|dv%)%)p_hAD);Ahs+PzJdHD<+$XU$Qw&sVr#`&w7!KBi@FNxe0 zGl{*b7FSP2?Q3DbB(%3pQ_QtE%Z$Kbiu(eeMaV6bj&KC9*VC#yLFswnxN_>DedFn# z{=WX6)0ZwWNgz}C=k;{u$L~Hmz7**03i^8b5qp!*kH1Z_3WZyE1ROtBkeS}{>4uKLkqP7Z)x zLJ)!w2e`V5Hq*MkiYK9PY`2oW(YG$ z6-riSZ?kDaJPWC6@OZW)!6Pqy(+a(GdKei=6 zuCA@s1&Kj>l+Jd1g!UY^7uSh6GksE+>{T|YP;vp>Vbv-O+6&~Hm?Da91=5T8|W8luUi&c#r0!fLc@RPl=aEgnhVmo{?>cGF&x@Tp*Lq;B`%+Va)i z+NU??_fPkn%pKgW1w@a5?^Vj)mWdE=ap$)|R{9(dWT#$ABmV_fXD^6x677G&=V)#( zVE8^w7#|KxbDvH+pMC7H#&0nbrABqIoc=$x-xgyfd!!JLal!)Ii0lG1miXL(irJ7^ zYf()bw65#ioSEzo1XV$U~orNx2I97R?WW%jf|KaaoV(c zRf799rDr*uxy+q=<_lz3ni^J8VDt^BNNld;l3jjv?^}QF=KgNk(K$FdIS@vR>gArU zfG4UR7)jg#*g1XO?#Rr@K-j8JmFm;qtdA^Ck5%2cTVAKBmujY2Q?6CNI>iT=hWZIV zQa4vm_D}`6UAh{wo}o&@&2_4(x2rR#^mI)Q^z`^G^}-MxLi z-923cBLh8d0A-hhsewq)-G}_wXQ3uHLroNl&IN^LGs9R2j6s#K-}8BS4oiojPo;C) zd8T){I^~eu>FNs0T}qelofr1|Wj4^$(>L1J(=)(ENBtg;%jNO-M|Umsy8Qj4yX1$L zB7@_L@jkc5eVUL)Q& zuHRi1T_@=45>><8_T><`0Mw~}fKaiak~_aAp`|G15=FD)K8N3>B3coeeB1JCRd9y5 z-Z=3H?IDxoeV25Aw@6lK6>DcV%=g+p&_Xn5U|jRjbDee~2!k*mJqfhU6#Zi4r_ZhZ|MDoKN#y7~6?L`yO-8^+!ihFJ)}$-lSS@uaI`f> zeLkhO)f^i>yLm*?Y$MdLL`JfPLFz$BHtZThi<`vWSH((J6`V>H@X|v=1H-Pea}%8# zBKmA=4P_u7E0q?p2Pb8wnVaItSJyUkseQB(=_Hl=p80WZ5mDcU6Ss7TKd}=NF4)AW zlD64TKn{`3^mp|Y*gZ0q*JqDh$6H{k>+pCgx7B07<|!Q#+3OGS2#vt60u#KY3xX)p zf{|P~v3v&;VfBke2G7j&<>mHHRxC=))-6*knm`g*>nzi24b5B`-b1m%&F~q?*|yeP zf2G-Bk*Qp-mv>0x(m4Aj`=({>5GD)1XK9jNL=;`zxNo*qG-Ay25VcC;ZNIEVu8L z7=Dqa%jL|(Qtp$~e~OgNTi~|bo9Mpx3HKr0I3xMl@3HR?rc9Ijmr?r#mJIViB2wod z-xla2FgP(rPt2jh6;C!pDl#6w76>^mRDNP2-5(n^j1I3OH8hlRcsmSZIOdQ&PNzq9 zw0%=0dD2ap!@iFG#bi3|l6yRWItEx{o*vniPA3=pnajzT)5W&?9^ZgCi+72(&lZva zdbz=t5u&{yhB5^kfxQg-4eeu-vB^)zCS&j90Z~kI2rd-0EL>uyVw!J*Q~1Pwi(Z9W zdn=sWWt#7YOW-VLNoxLx_!jc5WH~68U>yp{oSbv!Q|!Lku!0cVy<>+Pb>L+y2D|M> z4dsfpYf_EV@Lb#Bwm2sMF(=@0^m1e6KI}U81d%ZRD{b054p0&;aE(z-q0A_fj6$B#Vx-sNuA9((zaPAR2hyO#{JN9 zWUoP6Ub&9HJH1u%S!g;^67DI$ND#kID~7(sCtl<5H~d>ugRp1lq+s$}D?0r#L!8^q z7K)QjzMnQf-fr(8=wRCRp6kW07w)5w^x+3d9R46lXBX-C{aYi})7N2ErL#R@N=c5s z$m7$CsqiiI3ixB+V&B5(kkl(+6#SR*$DvSjq4{$Jb}AU_(~>jr4oz7 zFIZn=K8ki*C-iu!gw}pv(BoR^1SQmaY+1n;zXw4hK$~-i<1OTNwS<3~kcw*(0;`(z zVba#4Hqc`jXE7q%g=GQJ;ZpN)V zMp^Nkew2=@f@U*8$EY*YB#rl?W?Yr5bdpEkv;FlvZQ6w_d>695Q(I6&vd6|7vT=-U zbU=33jW^y9BSrpk($~l7c;to~Zu~_$zo+Q&-0JD*^xRYg@z`x1PZ2KM28YF)JOTK| z1HZrV2|;}yr{g$WP0{(>4!Mw1Q~bHWEsj zXG_EyiGB(s8$+oM&hLI!;L8J<_H7M;S}ue9v{O&$dg3*KVo#i4aQ!v744)P8S-(fR zQq;Qnpe+Zb5kiMW`&Npo0{av{Aw$(XsIGI?K81T`dqQqB-6BmqGQoRn>AXhnir~U{ z=`=Ixl#bz=z*TU1bAo0%EJ;?gxO0*VvWzxOB?#S|J z5{%`U0vPY+{80!)cJj05H0`F2bA_b~7nXM2Wbs9R2){%ron#wff+SU@Y*J0}TuNzX z`9?AxXE&c*0QrtW0Sc5VWzQ7S;0JfzB%jk(38K4XSjCa&smYErlW^f>3iEWFJEz`B zJMug=S&`onz#Fo4bSb@)nY8=A+CIVd77!=^_qG%Olf;M*uQf>k2~)`-S`BQq84&FR zHdzRW7z--RcC*mkQ^TYn0;_F5sf9p8MC6o0z3I1oK8I`NH&$E@`(W_K+b*0td-H{J ztlHD~jUGoT<>+C%X1tn0((THX)*!i?3P*$S9jt3hI`5-(=ER zW75daS6cex@*B<;{<@k-R5y8C{j1uz{ot*NWPzJRJ~#sF%`}%;=UVb-m4JFv7R@PJ z%hBw7);ijDJ<^p8UY&~aDzHz9e1A_q-_u_XbmtRFcK~?eW(B(dZNPFWSq6jZgsCM$ z269$`LI_eV@OklBM4Jlo|JjKS4=CK_$~IJQw}5!9c3{teleoYPZew%M_!a~hjzo;1 z%+OGVb6_iMgT2W8{I=SfLJ6t|E@bCLufD;Ln}dTUCd?4L`F`iZv11ot!+iVc4g8HA zRg{G|vRVPO#x!CHI&9VrG z?)jmifmnL-b&=>q2Fff#nV+-0;>gpNB*HS64yRBE4AK@)%Q7m@UXQs9zA2{0N2Wih zyZ!OO^LJnsuqt0rW0UC+Ui17)OpT?FzU~|quTxbHNbTB;9r!aHG#*nG56|Fzf01MyDfHckil>It+dL*O_N^n(J3Y%8eArEJ@ zohWf88wLi3yanay6LEiJm|MahlzaL<=It2lT6IP~-rdZ z7tnnEq^9-z8prSP=*C~okNA6?J#+bi4tJu@*MIa41B1K9-uTA6>U2Au4pfaeJkAbx zS7%qc*Om2k##B#-)6?N_db`z3k1IB$xSYGw*QBpujGvpOx3Dk6(=SN3OA^CJ1M%~= z4;Lb=OL(^S=aca+a_J?5o;d<8Mf;+rbrGS0KN4rm2~X-_9UWc$-X7TlPa0V8yGKKQ zcvRWlHyG^aj~eiOQX5cD098P$zf9>}-F|H{5>9kDGLcTFHtp}rXe_BZT}~%+Zh6q& zUVKt0!_(~>peGHwov}VG-48BVL2u{Tr0VVhomq=6aT9RE#N# z5=!w8odR+=krGe@%)w3IxF*_xlpXn<;Q6<+C!_PT3#Tt77JmauU5~}IL_BzYX>>R- zz58IksQk|G*wO`7YP>5tpLpoh?&-ywW5@p=T|XI%=MU_jj>EU-gYkrhS_%;hsaxu& zngP-ltwSIT$3%f7uK*@u)=r#$T#%Z;exGtUK6uIJd}|`M^g)N?eQ$O8E-l4Qz;fiG zaaZ^Bg$%ztwB+imh59@OEKf_pzQ#|pv$!a+M+6>#N7eF5al(t{N^q4UehXkDph5E| z>!@Hdi@IT;45CN}Ok=3&Hcf&sgVjTa{WVG2B$*SVWLuVkDr8IE+OUUXy6Chcpc{IT zjCblf9GIF0zRvYJ8cdsn|F6TY4jV&^O+;NXu7|p0V`wRPNQBLf;)2JjaGm1WpkSv~ zsugR+4cM1fiwd1!7G_)RJ8b;YEak~_ z1eGavB}?ziF2yo21&qfj)>UfA+%VR)-_FD`PY-2cU)A5~-)2zdb6@U{r={0b8dGTLF$wLNRaCPFNmRhOr1$iP5zy#*=XH zFcg*Fw~wuIb%g#HREaIa4RG|3D671oTiYB9n(CIop2DOKXm$At|vHhj~{14p?A>mkA2<%Ax z@U_kIR~a;6N%pfe62w`KFx8wm!q9>Ongk_bSqn>e6}s*r*w_I`9@n(D!R}qCMN@o?D zXAOkBkecvRZ{<-p^FwEx-q&H`h#0c?WfFfdGu%I< z4K_BG@Wu~q;5`JSVTA7+T+WXzHm>a+1@SJml+HE?X~<7f3PKHrLIr@EEVY*)hS}@P zHO1Fo9~~Tmta`DaCEciG4^cM&V<$oc{W&OSXmB(`6?r=?upE_t-Ndhrc7#*X;aK<- zvb7KFC}F;Td^{M0?ViQOXk>9QQr%YK%;Ys9Cmk~*_;@zCTi`K(I}Qe?m(cMI`@WCXz`7BXcG&&6}D*J3Z7 zjA4BOpZ|OSIB7axhnM%?l%9tl?on9KAF<@Ke@fUV96Q8Tm;i7uMX{MH8-7r3BIl%< zM;X-qeuK0MKTfHB;nNquRTR8H*SaC~g_r{Prvj(!tmlS@b9KPR!51A0VVViHWOfy+ zHWNs%WmE07NvqAWlg*<7YC2#+PF(#{D&_YnWn<&M4#@wSM7wcM_-dFbD_<2V^JTNz zszudQpzQRu2K!^O2OCBofdGnwSvFIkaNtdJKNUI*FoYiX(CQ3(I3kWO1Rv8h8{Zt2 z6(9r*(*WW?kw@7~I=zxk&oEe{C&r4!u?bC^9L?UE9c3nB{53XyC@6Q_#W88_>X3s! z#I326@o_~Tj7DKtxy3g|oc|c7ee71s;&GdfPQ~ykBza*2Wm(KD2hV0%V^b)Z^>KWWV%e)|zqpz-BAp;iA ztGQGv_o`LEzwxs)k%$S$k>br??Xck_wYF=96`M;4AeQY^4 z0a+ft$STpr&n|r?9*(n(#--?)vz6$Ri?LxSVE*F!l*!LdH#Xvdn8cdx6@(%F-?F1s#8ay>la;j^x=PoG zrV){_!yN0^FWSg8r(p`PfsLcjrp#0h10Nxm3C;xl0|v$`#y-YZ^Y1ig`310Qy%BQ# z7tQq<&ej%yxC?E2_+1wRdEn~6MkLVZ^(Jl}?8n^&ezvjl3QZvV^A&TA@C+18*UXRx z&_P3;ooP@|ZF3}2fW$4gBGd!tO=*hkGe{Il_+t4aD=JDzFQPxDUN_cCYX;MpROWER zA;nNa2FSHbEMyREN239bddOm-kW@p|Q?e*Yb0(c0YNjlErlav{#~bD{iM~F=WTx&I z=v(g_aG=Y26VOl)6Mr|Hbo)bz=T2WbeF;A71;Uj)lI-nG zh7z4FM1gg6CPH)`?{Fc8qN^kRmk*tK=+r4ltaa#ROPZB$SrN#DR;utCQS%D07K#;r z%oa2j*rTKvDVr>V^-HXiUpM&4z(p9R@!<)T={^ogwYu1=zCs9(FEScZfT_2FqyD2V zh~LsP5#stk{%&NBbzxg@vYeWv29pt=PKK~0#OR|vWU8rc;AWnU`jH^p)8TWT^o2hW zVD7(12E#pcgU$_^IR*%OQ0wk+yPprGoNnMjIy>_(HR|+@Fv>Z8<#n+Am{|m0lG3UG z91G|0*$`RX@7pTl=DPN##v&_C2wDrPr#0h1w9m~2Y$c8z#NpU-lvet~_H29TvGDAX zBJt|1O8{#t*z+~c-Hl&+JbZMPS}AV5DL?je{tzFR-~>w62q6P8qdDoYgnma%Y8O#%CAW=sm&4xP|^2rA(qjO2~nY``XzDjNT>e zF_lES7Sd}swT?l~G}#VmD!0pF5Bq#qd?UV^4_t;p@mMB;>#}bIuENEB0A%+`jwXsC zy#r>&Q7w=O7*?A_$d1cEL8MV+3eZ)hD!gBlna$OV-a)vnpDVJ;;{_&B4pSr?jH*sg z#Cqei16FvCnr6Zk)6`0Vg92{pAX=k?eX<(jQwE&nEc-9+on2wBcnL>uhe}V zsBUz1u*hxGQ=M)fo!776m!l)y9m0G~QA1iiK4amlW@c5VlS9lHL=+GI)eW^;jYjiJ zH0BM^3bNwA5zSziN!E%iF9ZFxWge;GpXdyrm&-soY=TvA2{Z)sU*a9$CAoxoyFfFG zZMR0=Z+r~vYgZ!~@ZBwDA`B$_HM;uA)m2! zi~}u;e7(x{#y=4Izz1Ug(dQ4xPfm8k!^USXhQn7_r*(b62**1nZ-|Hcq8GzQ!WHRX z8L!H=LgPA`v6cj(0A1VFqKWLuhEfau{7po!82Q&VK1)Yz*}%!hgpK0NT&6+z`TPsC z|5~w(^9^nrATt*2Ww<2ZU&edW1oOS{-+43t-8gVv=U!vYQ8T=KoS=5JSM$Q@3m={y z9-bb)#m0NZb)gypszOisVP9rIPBipd@~3leHBSdwKlyej}J!wmDaF7IRJ zo1B!E|JTI-VxwJ+U-3G|CdOG8J3t45S0&+%2{L9N`aE_pK43EDtr&c^zmug*y=i=0 zUOA{8T#@aAKPJCHj_`9%{DKagmZt`jR^S<4BpU~b1+eQg>BZjnzrUB&8&C8aMlbYZ z8-tvzxH$SwvfsiSA4cy*dD21D9T~Z-M*QISJp6vJ%7Tc^FzFUG#(k{7ktUt)oqI}$ zX<2dz$mRpBbs>XOWsd{0bmix+5*66-)cN?h-rMI1&SevOD%j)6% zXX8tPR)=cI5$NSqt}qWvj4U@r^)i3om-UtW2fW^lSN;Igxy5@ij81eP@XB!e2VUWt zogy>gP5qBPb}e`>-XOw1S({d@D~u%&}!(ccfV-*I}w zd?eB+M43qIpg?xVkk}IgMKBQ(n-r&e{(2-FrVsQqd$&F^Xp9VYcL2jRIAZV*oxxQ! zUPmg<|1Mf3-x7((Zj!oIW&JEvq_&4!-dm&8lN|2Z{mCfc^?UTyF4MTobPd$MBW}iVSjRbMr(iqn$xB?v90b!ixK~{QRmmIh-G! zBvZXup;20ch`GZvj#|wzGhBf`fg42|GxBc-J!sCJ{R`hSKUyv7Mg4b(-(1{@AvG)I z7ng}Ao%(JJDd~Y|J?i4t*nyxbTcnD|rd4Dd1>Dhb?zOS6cSrmm?Mo1ma%|2>#vxl~ z?t<$y1I2D6%I0Xc>#hFC+!)hzw;{ zVBXp@^T5*L;iNh+lGu|-45&$$KG`Tu>iSE+Sg&^y&G#HJbf5nK(k&lQlLOvF!aI;; zlYNIK8vlh2OdRU-SIRj7r(2Yl%a%-exYY0dsVu&$DS2?ji&Vp>(ti%r%RKUPzKG z(yAjk1uL)LMrFS|6mjsPhtG|M-ik=KV%^xPh?4Ac6pm4n^hbC{AjFNjXlZ~?J+!f zj4%UgtV~uQh#62>hvTxy1v>~At&nQE)JnxQCpYyft#NBE%B2pu7?Oi*V=Cn`yrcGd zSi!-vOu{-e{+YQRWmT+&_Lxv!7a`hZN%5)5Fby^>&&oI45VJp@q8j{+aD^FmwB6%` z{r8;Yrn<0fq4wvoYto~!&+y&%!@tLl=}TB^Hho3QEvr2GXw3ewM}?Ek@#q-+gh`lP zj1_4|cT^eF&AtPw4;6whtR`Z>5u~tnZAn4>}qWlkabyQ)mS%H zwJUI~1Q&PA2QVY3|5I)XrK|`))K-l(ZFN;+MQydQ4!K-~i*SXcv^M6ZfFTGhlN&aJ zVg}I0OdYZ*>pHC=z-Kevw&(5N0im6X3O-8dUs1|*NH%|Py{Exr79^%=-2;zN~OPpar=A<7wb>x~BaqRKgD~B_4D6i2DbdUGkx_IR7yN?{@ zmw|_v$}AiM+ZyQCABWuTB&h=R6zn6;0=|6eY=;hgno{;&+BJTQb`t&0fZx^l@6x27 zD)3<}9g5*yls-l2uTk1I-U9d=K$nz@)oT1v?J;54iSa)=sfXtfLl*Aeh~4mO`gb74 zA2VV%tY4Ghh;lVph3=(Dj3j2uLRW{7e&5l5?S@zl4w$rlLu_*m=xG5&q`<0T6_^X= zAuFchbJTA-$d@O@qdcPMs)KqvQs*%`g1aB32#j>M7;O-3qW*L9?musi64Gz}nT3R& zZI3#`DU~EqA}W|bz&Nu)%drB{Bo9;i`Mr(xy%YU2i9?B*{>EQ14Ov%12#|4p0z7n< zCno$eeSI_j#vd1p=s+mBn{<~0jss|AOZq%NOz<*NcYLw{rG5xw~GTRD?Yz6qchGMqBTv_Y6 zOml$fa)a!F0>bI|TMwxduP7(i2*c_SLA=uOQll(%k-jZ7ai@$5hSwK$lq9|c$!?#vZ zN=VnHFf(`NB4*`7z|$QU0m#) z>D)UxxwrG>Hr>M1tus>{F5gd$1}}{UAMf3>r+4NI-gw5AYHm=iQs1pc91M4-N`OKA z4h63O)l_b`HXN5Eh6)I74@!IadZjZX11c`<{L<-5%C;3?QY51Tz{Gg~`dHq+BCR^` z_rDwJaNYOsziy2_8j2|wv4}Dz@$tm=^{RIEhC;oat-jHTYU^v#4s|5#!Gkn9hR`lF z&2?wwLX-zLZ}c3p4G`xOX>Lu8^A!6hk0%d?hJ!=C$=6T%5@9$7cgXwMaO0m6=JJZE zRDOhCiuAa94)pdO=ymrF@Za41!m^owJFbXck5)7a%>H`qfHvCS&4|++t#m5*j(laX`$xy#}u9ZYT^_q%CD(@ti67e8`ZDY%1SR5v3^pU zyxNZ2*+YJj$cdAjNJXLmGqio96tvR9D8JEo?{ePSfxy=&mW+Fj%#OvQ$^0_Yn}={6 z>bFnMQk%?=EBJAMq# zOt^Zlr!yW7;SGnUwRmi34lc){0LC}l;~96le~e$@-#R>rUbjfAP)zVN$0jUbZLk8o zKFEM&DJVj-IvZMbcJ|mpW-2{h)av}eoSoe;&022u$l|R%HfnKRkQNDzIl%#gGv&&?GK36E}Sx)AL z@F@lNdFzDHNSVr@v8O zU$25g$hvNtqGbY~4`c!%D72}HfZa1&luPx{q3YpZ6h@nfzTHVEg*RY7#Ks{KypRhu z=Sf>!$`ebLt3p35TzAa@ccc4UrH0O)zJO7^;z_`X^mXVa1k{Olj!!8uW%6o=gUGT(adg zk_H|R>R3f99oXK=*331Ntu;1ksafX7Yp`9?bP!FLIf>SbGW$0BR4YHqE+iM+GCJ|3 zW#Gg^p`V@3h5WF6s+U!I?pR~fy^VjE_`-0E&ERF&?i>B#(c$40*XZjWKj1T($Wvu# z@qRu|pknPdMGZ}~C^FZt*ycnQdeC398kcRSL5Ihc!I%dj%!Sg3UC z@imvDUB?D|;l{&YKVXh8Y47tzJR_A%q-qXSy4>D-h~TK%R8+lL0=G=b+ht&dH2jkIRg%!kQv+O4D_xj zCND#a`2tMhc{V=Xs~SbCoZhC*<{zL9B2mODwGPl1AhMYUy%$WTSyff&S`OY{&VjEL z4m|AQlZi7wtft&UPBp+ny{YNB>7~$JS4Q`EVBKbdOKzpBPrAeb7IJG)YYv}yy9%hpLtpwVn=4-Qhnkq%DD$wD*CTaqeP zjW0hC$qWTppfBd%6;-VTy)-SN-9wmNRTw(^ly7Vnno@A(Mk9Kf9Il@q~LJn!Bq5Ofg=5o1A6=DT8!Sl7JKcr5|`8U9FunG~ozOljkX z&6i@am&_L_jQ!;oC8uSX^GOTWP(l|W8K`y@_u2Ubos^e;0^D=oGOkBXMvRR+S>O)+ z^sA>g_U_fk;Tl}J;|~4QsTS%G*URaft=F=!;X0zWA%$)DzW{VL11C(p{ZPeFIuHxF?)j zoa))-9h)#a8~>g41jGGZo&VsK1fMPiDTIIm;VWBu(JXHRCTDpAkWBJdvhKyP@qM5T z{nLlx;h7^c;Pv3stK%5HJv%xNPZ{?A^q=74H$E5{aKO`teLBqoMNTCUz1L5clRWqy zP6AEwXU;aP!XgQ)w?Oq_Wy7del_DXOcCTw|XjA2nTqzj_7*DafVd(n0VVEQV&1q;< z753A+&*I_hg>FaBzO{6Cb7h-GbzXC_mzenli}pdVu7F8!(HJY!L3QO9q2+#P6mkfYunQ zmr7)j!2ospJ{k<0ysSGY{yIqeWq$~qOtXFj<6)sM$q$@7`GEW-{mg?8UWEg;1{c26 zD0!dw^b?Xx_-2^ZNFn(119%$Ujrf^f)eNO&htz_)G|AX?m&rq$;%jb5N0JH~S z61*SWeJ;nJz$xNNlQpVUe@|;J$Z_%Re_kx@*;De;n69JeCb)O9FkV}{L^Hvy3!~ZH zS&q&52;l^fWf1z%W-T|CCiFys)%T}m-4iYq&BTkvy^F=;i?L%D?>)MgJ#c*SSZ?x; z5?n7GIXo9LP919H`8?E9vSg0gW%%WXVlNjTfjie?zf-d9LmiS7C46s*@o`U}xs(Y0 zC=?~AIVs=?5MGdE`4CkJFA!*h@UU-k(wFj0O!|hynMhf?AruP*0WfE+!xvCvAz1d8 z6m{7jkw-@4Fp6N3{xJRox3E76Yp7lcb>E4E<(=JlyQ2O|#NXAmZ(mmz@;N@yBV-G{ zLr&U7Qc&*MZTmbZBEmG^+RqWY%+KwVOH~dh&i{1luUc=E>NPS_UaJ#)5|hYYxk%UA zP8xM)N`h}{Cr6|uN{)=!=fLEL4wKNr^KEcItT=dJ!PMlRUpP=`)E6E@sx$pA9+AFp zM9t^NV~qCd$Zoi1e^5&)nGT6nEGcM8nj-BRm6Em!Zbd3bO$YCKHIk}s&NqCwlz%dq!#vtgQGM!mJ^*O~`)vTORcLSfpzTqs3N(d)imxqnQ> z4)0KG9g4kw$6}i}i?2ulk}i-vI`lEyWes|POfW$(Ty;Qb$W5TTVh;S?OOdLsDEjK` ziLPE`CwjY1%mV9AvL!oDne-`58Fyiu+&z>#D^A`xSr-ZbCz4Xd94i#Y%+R*QSf$jc z=3&yMWMRV2p|M74_w08oA7k9Gf^=x_cu zb2F!-RoXy*KieJtkGrC}qL;@Ki-Y!RLGkQ)ybx)GN-8K@A5kS*CCx$T`bWaWlJK0G z`$+7ZyYaQ7ZryzjXoCK4thPUHwv>w*_dPdz{yswz+7>a$Ml7^p86CCM>%6=C>f+++ z;=9}5Ae+i$j%PB9JG{u9<2@GSd?0Jbdz1@8yvM9c@gB>eQYlmhqp;ObiDOg1DXZ~) zqmI|g2ESvC?iTFVyE)<#*H@-OR7$9T)_ZD>%YQT5qPa=q`y3N4;6Iad&7(&*L%UV> zjmy9e!m_d6JTlr~-u~6+Vc9OPi8eb1R_#kIuQr=&$h4iST>Z*xMk5UB$?JxK9`+Ei zmOk{RAO9!e_|>B$kxWaz~#o;?~+}3eG1m;%te3^&Ji!z^d2DXx-??_GMj5H zEX_vk#B3CfTJaY`ZttSSqip5rYSyKL_=P0Z$Er{>D#x&gF4*n(s&R5(V{PAY%Jpp* zO3d{j8tg?j`ZYAX*S?X%Z@!T9sjBbKfLIAC734YWOO_*jDk4)-`P_ukE%W?nIf6^Cy@k4t?4;ss0P;q!XnHclB%8UBAHrCUf z9|VupxynswGW5V%Z*p>CI5;O-nA$yX%v!-S!!Y%S+E(p$qf%VOQ{g+qsqToddarV0 zO-f-U*R-I-PkhJF!@&dYkxoF_}3p50+Kim-gXOUb{7 z54(tu?b@OIs+JrZOPb%y6T@gEnrXtOnhJvT1W#qUvOV=AtMC_6>F-B`|k35`u-{~v&bien#-S=Fv zCHD0GNS2_Y0SnxobH`HHZ*Blb%7MBho3IS^(XsL5F#{+(6mP4M(6b&eZ2XII< zppEhg>97UxNl>BC5jpS{lMqTw+#I@819xE#_mcP%3R*8jWf$zj=l^OP^-%_yO@b6ta-oj#XuK<(;* zIZ*ZYc1OKF^$#tKF2TovEQeW&yn!)IHcggmg!jhGuX7_(qXDW@1_Ue7D15B7MMaYW zNDI43X_r)-77*QQuQbXGm^|pLl?@Pr8L)K08e6=w3P;kFE4J-H-SXB?x2%F>vW9Ad z_*HD*0d|b$qkLVlO{8!H)bN0t107uhi>VfzyFy^eZT2W}7_$~}GH+2RSu98xdnS{> zbFfBK;~()tc!3o~0oTEYiJ%n5<#wZ}kb%6LQIYI6{)v~S*o7M}u#Zv}AEwcC@8Q8r zdgv;ZcCTfxN7{m~unlXj-34{tgb|R>;cTep01}%J1VU{#!G(M)=J!WhkO4=6LH9`K zm1Q}77QqB+WuyLQp!+;L^;-y!LefJ!^GkPaG7QHjdAz~W<5Bt!^qnBnQd(6AeCeEHs zo=ZqVIU+`>KnHr-%0%l}88)WS1C0rVvI-RT3YKc{r`Qk*J_*Gopjap|WtGSgjgsW~ zN{}@kqFkIINo`7MX|;1>nIsf!*(g3S2(`ZhtM&ive$_k_>J^&f^>+JzbrrvQNob6>G~3@plJUC3 zMYMDTD9KsrWXmoF404mu2pLcx5D!ELAW>3)02>UydMd4SI{V+ z(j90XeYp;x;LCWt%u}DZ>Iqgu1>CM@m4k9EFeYiY60mh*Bp-?I9NjCYP?~48&5FGu zc^|B@@y0hHb!$K_-h47GY+s9V44u7WOrrVq$sH;p)`aAu z>6Y(uQx?5#4gQ{r)!=V!O9NC${qr@T?$Oq)y->kM(IfSc^dnC=_ur+_!Tz$`vHio= zzzL;nFlnc!+*)FR`q2FKOO!x_WbE*k5qQ7;UCX0+DrHm4*DtPKjlH)Jdv5#UD%IF~ z3bCCEY_pJK$a0d-ju_D_iMC`CZGr6^dtdaPBgJBVx%VO1;&j4p8Jj(Fk5MWb%lTOB z&~iQ*jayeFAy%|U3iFtsu)-F$foXHn3(iI;^zeH9LfOGe}Qu8)#-zh#6Mh z8eaz9kcFJmX>k!*%SaI-sZ_##Vi~H2!HUFnH1Bpvz1$Y75D~|qR_34#DKV!o-&u&Xa|KA}n~o$hbSoXb^(Gv;?wHu)Up%tt-(#Kh z4y0mJup~~!QUkqA;)(;U$E)ay+@lYrK-JMB!-=;CnjsaNbUG(vDV&WNy!URl!Twqb zS@u7kY}Nw?wHfqhpGTTWW`8L&?@Vv+mq*UT5`DqjjaxGp5;1>o*%grSa<4y@xRANk zxV6705j!&?M1rC|6+qy15}wHD+>usOK|AmY`1ZG1SSrGa(Xz-)So^$)r{dsP4atC< zWD;t%o@IRmFz5aw$suYj>``Q|@SNA&OSB~CGV8XkgVrW7`lMia*A@}j299O`HPc#~ z>R0HmjQxOSunis^4k9Ndo=+%=?^FMU=OYU>)Ar-a65oy~E8KNg%rxHvTkNinljEV~ z>?C6N5rQ*ePj2UD!EyRFWA&j&RNXW;WAklYX?wX{v>%!$Y1<_#;HT9vAz?Lerb6I* zfWN0vC88JM{U9xO`jeKCBl?z{2(5-*VG{8rtg7pZ(x@?s8b-8_c92y9MW4$ymmjrh z&P=4qBaawsYXIGBnKVO78kb)sH5)5Jwd}SPo=7HH)l_R`YmY&*)Ae`qkjVsT*jU4K zYReU75Pxv5ufqg`MM!*&DlrZB(FtAN+3R%Z(|>`x82PQ0*+0S^c+}0QT81~ONXd4@ z9*wb!@oUm!@tdD{Cicvq<9UpJdh@S68+*3R^C!+de*!Q~Z{vDHR2jaNtGcqu>n2o2 zKOa-y>~d2pmqm$1II!$! z7^brE|69-&;G50#DfjdRo~AuUHk&&06K6(g*uN6&?hbZ;{U^@+1S`_m-`|Z_NE*Yv zV5X?9wxrrtV{o$;jBZ2&+1;7U?%9KLdk^m#oSr;X z7@9dWF>z=nd(+aAV2NG z4<~eGesbEeGJ7zzIGvBj5AU6$VjtGW_e_Qo+F&R&s3k&^d&YGKyYbM>P~p(z^k8&p z>831JM*6<{57>BnASbou!z%Hs+XLsEffBon*=*-Od z_(XP>S9krp>~62_y=h@DUHj$N$L|}Wqv`a>f0$0spP&<|d(&*)$2nodogk}|IcY)K zBT057ezzU^!EJ}|m+>lGp`dRRvPb5j3FhXTVVDgaL+~>R7YT}_Lgz4?i%9V6CWX=E z?s!P4KwNydhe_)g*Pru0c&hVQ{!GHlJW_K$GO$EM|gNB86~;KLZo^l1b#@M@hrv^}PnyG>RV0>B1tbP>nh{9+c$; z!ENrfN(J~|eWOw_&3~z+*R@4wB8{}+-Z|Q(^!vsWfC5@1WT+x0i5!>D)0JPPE7v4C zVfq$%w!*am%z`J%aXd$ub>OgoJ^@YD-2Nb_B{dLvc1OZmIIJC{QdnPb5F)aspuvW_ zqtRqnGWvc^W2;n9o5U}=Rc`JUbRnA}Zuw$`g8kVfLU#&ZSQ@`NX&DBI27%o8^vG#V z{!kc6Vvb3P<-S{Xqu^#CHokZ10!VUY^djKpzXEtvR-3il}LJuYkc+HBB2vLvppP)G9@3Qrb06DqP#pZV~!H zO~b4<#18Nk)7+%#jltXDu9$@#$c&Bk^Ote{CymLl3hzd@5`IEQQY zTfOa=$8*d%wl}e_GwgKU?R3r#cAxFu)fwEINbC)Eo<8Pu9`jW3+GBYBd9Ixtj14N| zF9a7x&nn{zeBL@XKE6IW5?okY2#$3 z`FiZ@Cs%cwAVs}?I!gs7JTJyD#MbfnKRgRVj3=Cpz9Qc)$5#N=E z2jU0+M&r*e(@DB*+grb_93cq3(sT$iacypu_hqQW7?gRDDpFiuXOd7JR)fmqRe{kf zl-xxevxjmtE?Mht%Fa zi0l`N_ulgP?QnK~p${;&`}%tE##@+gJJ4N;@j5sp;-I&(NrX<$1T|`B^kt-3k@5A)o)vM5OhOq=2NVfC zBChs_k+o{97s&&M=_S)#=SAuDy3WneelR0b@EsH|>nLJhTBaFYR!A&a;A=0J7qU

wF7DI|Kx|V1sBQ9FYs>m5C)C zC^&s-;)-p5xIz9`m{?Ao6W*g!7;RwcsCU8+^e@V%X|~&{eJJdJ*dgd0ikksDOa=7~ z3X`}#w+*#}%7j1Ga7a+*LFono(N_&|d8I4|VUf%O5CEQL3WYhCZt{45YBo59;jgIV zlaD_^rk0DgQ%ufSz!?v!PKV-jMV!4ZkLGcCJ0os~;&7^r;TH~f#OI+eTs_S%P93=2 z@%OCCdX{OPaQL0BwA<0;l!sidA(yAi;ZD1pe&%(_tRKE|Il8>gL6>XL(b46AQ)jErfZzfDG~EcjEKKyQ_|x>K*4CU8#wYBq>Y9>a;~-;fj+ zFi@1B$R;-#%L>z%^UJT=5yBWe2=b05K0$58SShyGQY2Nv8EyFSV1Ao;pL3{0w- zMmsvk^lbz}QL7m9?H~-dO%vdR{XCrG>_%C3KE-7TDr55-8vH5GK6VXw-A7oFMy+y7 z<2TsiMbWR2-sbjNPPdZUqTOW0wQW?JMb1HX!FzlS=Q5%y0n`(KMiKidz$z;%#g&E6 z7Ws|<#qVnTEvBqTY%!_}>3Ld62wd5Nb$RL#@IHrP1>k)O$2IoDyDwmLi3_`96GxYT z8#+3E0|;(^z)0lIHje{|kyXSNZntZt@6wFOD3&kniXH;6f;Q_jJGXA~?j*!(+fYU& zB@XxHhXK{yQ7?jE7JTu+A-uQ&N^=EcsFj$GJ;MOWZ4JKHYpqBhbsjI2Fc1<8>s!C!1k~Z zTSzp^Azv+6#u%*nhKZEn^%|*(H{jaD)tEdLmZ>SQVowIUx`N>9*bCsA5xJ*1J~$8A+47~40|8+y`ra<9Xa^SB1wJALtc;?!S>*ip|U z{=B3c;OLgAw$7iMvyD)H5`&5#$i+sdme7I;HS`;l5vxJ>AB{z+`xlF+_fZ`skA%Rg zPdKm~x2^r$9$heiJdRD*?HwK6D_{#6`ns-bzc+fC$)`tex%COa6?_bF1sjr1e~>pW zWTr#fNyjRpo1|zXWD_zLp`@alnyFW5wk#6i02fi!ZkHk07`fpnOg1_SHj)fDy`W@N zaq<9~A**h)CLRucII&MY{BZKN+a838y{boUyDj zAK_mf=^jCxwvnGdzl03R?#L8ccW=6# zmCb>G4o`1ltf(ryU|2gEMN`uQ16BA+3k(!B{H_~x0ZKx?c(IqANBJjcPH*SCj>fvC zP4r&8C?^!U2ani3>n7>{>-86r@yV)!Mjzi)4v3g-#RsTrA^6u7W6e-3)w!X;pJA9L zZOAi7l5Dq0Q^$~%a?&Eqq;0nB?b6wh{XHMARI11N1zRG1YA>aqBE!koefjz4zx@0M z=t{M}2LOmL;jR=lvO|8Fj{o2i-p&@E$NN7?Uwo5(^faZCXA?~wf{{JAll@=-2mvLF znlv@lPGN88dNI%P`Mjx@wjs3}8}swPHo@N)<~gM&qP~rO54dkxGBOmg-`cs30bNIN z_R98*#|zd>S(GG>)Yig*N}_IV2kPB#&z6SXc>?6pCt`a63uI|R(@=WJJ~?**J%cXH z#WKebVE9=2T)p0~XUvO|!anVgC?fR$Jtc?d$j;02{HQ6=Y)AK!?m8G-cyS?ixMTdO z@mTy~e36zE!u~TcaY%<_3-JBh#^LMuCvCfjYZCT*q_8D7u0F*3l1!FI!)MK40y%n0 zr}cdEoOGo(fY(?B(311ZBL{CiI0Hk^O;U!c&h+`S-Xll6XXmGumZm_v2Y(yDWkfQV zG`^z?aT&PM!V27OF^&~6Uk z1pRn|Qx!ByEF^VoWsElv$OYKfVy`?9yYWL8#*5*{1}5Gx`Uch!d*uzWQ$PR6tA>Fl zVK9%2zG)%?t)tmW1E=pF8@vDXz{Ly16`1!O?pV3Qd-%S27AKD2`xV26-psu zF`1xugKFDXU^~%7El{L9+h8w4kBo`h0U=JjA1o%aJe;6lIB1&8H0c@G%XZj!?425_ zpR~qCv4#j$B3;WdkG9gUwQ5~l?aK8c!vAgdqw8(v#NT|M6>~lzWyzjm4ydEOT%N$^ z+yZPe_t@vgApvW1@;B|YZ7Wo~2GwY4(O6kCvDfI4#zzT<1SVpTOx8)fYwDn3uuLwf zV^!fh9ElC+YPi29!5$`nBFF^E@Pf?s;J0g}gp>a5<2rI0ipn442=deW&_TlE z)w4Jl8a|0MY+u+&NTKPA$64QBJV)p+GoD*@An7~dYTenu7=jW-?yvo@vC3-wqBzv`| zzhl)eJGwJ<$C^Psja!xwB_Z_H{&^-iLxkN;iG6lU|l0m{{2I zNv@xzjaBG9HO!WN7DTZoz9L&WyBX13rpP^z)AcaLL6g26o;cIX#qH31B=lk0O%&td5kyw~ZxnX*Rg(Nj5^K&!`KGj%=8q=n zm-jSjzk+>nUcAaaw1kt=1tkQFd1!D1r1;@j21?mGxetA{XW<5b#Dsf((ig@j3;QM@ z>=#<_B%=Y>A1L549)kjuKe~5i|B-v{IRYVHH(~O1N-47FF9cGw`pLw2qQfRgh?>51 zAV^~84yQsZ`oKK{`pOOd1LfEoMhA3da5D6rE83NP5g?Lp+jUJsN5==o53I(@w^* z#_;M&nN`|LvAMLSO-K9lI$`wdC`@K%>tPjqSB6fU3MCEjz`Y)2JJw3zsVrfDq?R;xgO8Cbr#d@*0S}K)`)&b>dw&%&)lYHd_c^T%3EoDMOZNPsS zn#(jz-1v@YzqZ_HhQwT`tzlo^*f7hD3N<$Th+ZsNT#3JIK2wpwz0A7Rdhc{sFSns* zZERz%?L5_X&Il5j4CdD{G4OPQjxb>rWFYB?((RA=oVCI>*o!vSoz0C1Gqg&sH}ii* z6lsur^#?z04i1`_FoUSkcagvT?_4-`>;i0(#pPYKXt6ZT(*d#qx13%J*;b5n7`t=^ zMpl`ON`9|cDEE8)U(QJ86TW@p>Oj)#iDVofin1r7?tG6vd&(RP7kv6Rf`Q5GtBy@AD-cnTW^xp=jgXQTJR=|Ak{qQx!C>4veXS!(u|F`mQ~Z1 zrf4FfvZ|q*x`8FaIBPw$0i1b%xNd6j$DdT!_0|KDj6fH07@X3Og_gB*S$b)`RYHkm z56s+}Ev;?Kq$NvmJMw&X8y$i57FAYWjh8*py_1PRknCAbTsWIQyKDEEVNZQEQSS33 z192}|!4!+T&Yszw%aZQMj`8K7HC9c^Fas}^&q-Q7OtK^pN{$nTHX&+_~vjF{Z($RO#7+dO6XO;30CQ)eFV>fnys5kK7-q@#MMAD*DAwt_$(tDbNY`^Q*Pm0Krc}f(C3R8EAucG*Vb3n)Xt0}P z=>=qeSzBINS*{~}52XETkFKmx3soDs}kGO_9L^mXvCX=l#0qbq{=8UF5Vj>(WVL#%W^Y z7Y=%p zw^43Va~Qlv^mh2h=xA>+6H;QMFd=1<0VU&fJ32SHJw$hVcKf@-f&OXDGp0rZ%AoA& zbaX=dEI~bf4eBv3osjO4o|4{+qW}uv!gA^w+$YO}+6oWF$$^U4>|4p=x!L4mY?Bm85v4R4^uc)PsVy)4_k6hCMPrVS%B2N#h5%9 z@bx%@&c0sd{M_;Tvhx`*BO4vmIvkF@g)v7@M+b9s`FchpxvtJ#E@!k)J$m=i(C)Ll z0|3?Ibv`e9T#4z~$7W~Zo{mm;bYk*>$%#QH8+WnAJ^SZ99q!#n_ZzZH_a!IyBM6&+ zV8FkpG?fjfM$?_1j)@y%6Z3Z+j*N^%aB5!|9qeL0?~kPC9Zq+b!x2dB?)p(@G&VXn zb?DGkXJ-~V9)yb>lD$sm==4kuL?Qzdoo-J@R#n-6I_kQ_Vlk)O4Pp9?gHEZaK?i|Ay338F_E#M>A}lZNJhO%zb8TS#=z%>3i|r5nd*aLmq( z-?-HHvZBE84)$y5HlQKdwqL781gpc6Wxz(~Bw&9VaU4zSzz))*E#TV2L8o$LhYOjJ zqlTqewHX0%@vv#VYy0!TxqL9cU#X#p)MN@u=qjX!sg;SBr39$urEGR7V}KR~8ApUe zCQIi2frfeI3NX4gxD6AWOYe~+_9=McLBjS$;hKk=!4Tb>Q=877YI7XO{AI8o4)n2p z-}}2!`qjyt>^SHv{UGVmVTshhWcc$PLDxgRUi_N%ehU?#rek(+4v4PNeDpM`+J!fb z)M%a~h2sNTQF~}e0`d}Qk;sOH0zU9&qr2=N(Ea1y-P!S_>2zQq6H$`$T8POWkpC>q z8qii{e}o{)%`~_Vg3sVM5O0ypz}E)`yP4Ay&uU}G0k3~G;{QXAU+&=iJD0wbz5-v5 z%!3*;5Tk>08zdVP;m5#Kj8o}sqFP@+b|F54wQUzsP$77h;>HGPYROH9fuLA}zbhL3 zwfmQGlyrnz2bL?F4~0}PuxZNYm@<7_HoUJtZOX@|Pru%Kb@s*^X90cv%mebV>C^Yi zSErB3`{C=idP@(Ky!#P|-P@)kKnlYyV4M7--5>Vee`?e>cukP)k=rA;Y%PE?b!0iZs=-(k4iYR;=3=s->K=!`|lb z9`+=$-#@-*kDLsmjy9OQHny;Iaj$1F<=vH?SX!F+d;R3?72?L-dO(GPfgg76(I@uq zoe1_Xrl~|#((F@5r#DFg}%Pp8p%3Qpd`A6=%RWD?2zb$iY_6Wr- zoqe2mW{qe`ova}aO3U!BW3nfNYZ}^>(FzCM3qLS5;Mzt@UufR8m}uL3tUY^^qubT( z^sx@7+u47?>Kg3|c^r&6JaBl192G9Z{d557JRLymR3)7iS>4ieaXOsOW+A)2 ztY{b-w69hn;QtK>)^!D6iT|y5+C*`>Dtf0fJLasl_t>brcAh`Bw3HejPbCr~Jv~2% z*tw-yv><2o{ne%6+&iYzsSAmbz(in;P;}ozcIT4RWz&%2s1R`SB}RHiLJ$lwKA+HL zTMNj7oXw5LgxR5IBCD(8`x+)rEHpy+AJZr;uC8JfoW_@|t2AnwPG2RQjz~@^k*pT9 zpESd9<|!ZICX%#d!6lEZ=4|DzQw6It27Jedn2NZdN9(eB+TYb5Y-R&o*+Ye?JobY?R5JvgcM<)Dy^$@}fuwZ^Tz)uqxhaiB0Dx{$hGjcG&oLIUm zxV)dS{ma3-mQKurZY6u5|HFLpj#{`Vm z0kTZrFBOq`!!e>Z)iUsAU_*ie^fl05Q*j5ZW8e^~aH7MK_hnlXw=JH{HU+pUDhhrn zJf_|d?Tqj4-5v1jV99i)qu1Bxa292Ex36cxanqDD6jWj{CD84NIKs)1Ty7*i^()w& zstUOunSmk;ft7tI6v~e5>f04q)O|k{@b?UPy=vc7SMQN7SJD@ZYw>OtW@_$OZu&<+ zBm^O)44?u+up`P+V&7ulA|x5YpJ<}_Wo@$*IhRGl6n6`WknajW-f_H^KdZ4gnWg;Z z1Nv-$v6Iog-GFn_ANvH_r%c@*<)$g`s&UH{T?gBgPeu2F?`^1ih-_5ux;-kQMyO=_ zGs|5RfmkECFAY_A$8GL?5)$OQ6Vc*ua56qV4nXE*UVsXcvN2+PYk6t zL)K6Wc;KD?vE)ZhzJRoXHV-M>l&s3JahyzsmhflMMRCAix&MR8=c;cR)8X$P_6yM` zYDMTgBv}iyimvEmZ>i}hK=m|^M4u?KRb1-@GR9h7n8Bc$uHRGK7tNZr&(TwYAcX%hr@gd5{?;@%R_=RkP1d2kg)pA zhhul?cgGKFhvRqacf}6h+DWe>mx_Bc6eoPdLOgHCYiMco9SIGwQ(NgJo>j1>Zxai_m1Bo?*cl=(5 z#NJGC=eg$tJUFij^lzEd8z{r$K3oMD*X*{Hg9lfJqls{6kEZQWjt2H5`IY2A^9pK`W(c6r&6!=CH#hzow9vYZ2bE zJwpptu!UA+fBQ{m#JzBRi~Y@6A;|WPLdri(5#Xr}y7mo9Zxm8~g-vd@C>N}M(nOV> zlO&F5&YeJWe5UcF2uXLiId$hkX<$=G$CZK4oK3f)cn3bgkv9DE7i+#bV=j5`scz;X zCLVU(r#7FmvMZs6UiYTkLu%6HaJZ7He`x;r?%U|J@#_RFbPJ&i)d7C)hCNdZ5t66& z*ayo4X?bejz9~69;PrXoBr`C*G)-qw_?7)3slE`iZd97s8WBAW6Fgs4J1Z^q$Hzmr>-w&L zy!(hS8zFCLVU@@<)7gmb1)BZX7h@B#SbQQLi=X`B$yjXD*;n9*uEgLBu8C))`4(bA zg*l?kX4$zd1F^KvI@kNmrp#2XtRsYP8GCrxK-b+mUyFF__42q}iV#&G=eOg2v9dY2 z2V}&C&dsse+YkJzW1x?sHu}=cY&=bU7p;SNE7YVODMq+KnlvdLkWL`|FUt@*5WR$Q z>S(%U3SvL2m; ztc5IveOFZvNndexcUz*=RNEfz3qkx7k2zc5~Nln5U z&QadCZ+=MAhWsJ5FBuyL=(jzwbYfyM{)_(ANw+JiS=ls61`$@U(hnuGQ{mSQM$^SbxMg<-CRN1g_Kq`v1v+i z9jcYIYk8YhKeca2v#W@tr3QnlUCDgU?$q@3$ShP39!49A{knmFVzdRCg*-Bv zLWJD2$a{dYO2!MB3=RAK&N6Ln;|6WD2nU!IYJS z!2u);^b$1&zfsvW#=;Iquk7e>^r%yQSJ2@Ic7|PwOMNEgb$EhKHVAW(C*8H?fLsm+urvU78w^eW004LaV_;-pU}69QI0+O% z1n<-)>@NtICO)nVA%tQkj`;9bi*sKEb3;O$YEv_B@8J zS8dKbe?S^_|8D)3Gz+T$X8EtzUiMO`?4?p^@f^=yr^i@;!d^zSKHw^4%vy~H) zDOinpKDF4KqfpZ(J=98wDbZDWh1g4rtP;VnkYF?S8Je6&gMA^3!s0mu_Z#zo`VUMo z)278>Q`EVsT#wd>$f`?aF6Ulp;zne0HSCV76Y=2HRl<6LI*(Lm@QKe6ZD`f;%5{gC z+K;GJ#)d65>T(}9qmkNLF>|s~eu;0P3Ux@k=JTHNC-fuN>|yhp%o+Bwff}QGV#HY4 z5@tB)>Bk9Ui8IR)$Gn0;q3^k~d;owwi6=;k>WBW5XbUkk!F zlyl#9+}BZ!O%$@qsnVcPoNWt>c^UGg1EV$hb0z9)U!8=J1T)m%&WWv#Z`aKs zz*J&-FzcDCtcxwrwq>WVTiL7ZbM_aPoh!<9gZbSy5iQ{h22Bk%iKrYZ#>wO$4L~1LIk+w-s z$&yn z`cQp`{?t&68pd#Ai}Bc$%)(|LbESFG{9^STsm`fs zsXqk41GH5E006LT+xFA7Z7bWhZQHhO+qP|Ym|cH6TH|+&jE#>SkNu99i;qd9PgG8f zPdrWP$$rVlse-8isb@fDAO?g$KVT(r2KWzF0wu5`I2+smUWal)2Gkpx0H(dOu1tIM8hS5%j=o2~ zqyI7mnXb%OW(9MZ`NZaB6}BV0hrP@G=i*!=ZXx%E&(9-#H+}|xT__=NLR(?Ba9DUP zW)qX5BQ6l{OZg;HY9kGhX3H`8h_XnXrY=_xs<*YwT3idXk=l0co?cA%^vU`uBah)2 zvyC%mL6bH+nRCqR<|nI&MO%%nA=V1(w)NevXsdR6dxSmP-erGq(m9Y5IJ2EwZf>`Z zyV`x?mGoM8+q@6H<?`64I^qUO=YnrQ^V0{|2O006LT z+qP}ne%sdBX0~nHwr$(CwG|v5AAWK~xe@LWb4DB)@y6gaD29E8&&J%w9>yugWybra zoTi2*r)j!rx9PpPlG$U{%nQtW&7UnfEu}0zi)vYHxn|8{ZEtm1M_Tt=KiCG?6x&AI zQ+pM=#V*)4**`g|I)*q#J9aysIQ`B?u97adYpLt9JFk1NJM5n8-sk@2>EMZb#(Um- z4PMH-!TZD4%cuEH`m_6+`AvS&e=krg5D9D#d<)hJ27)t!dxH-{Swc-i!$Y$|S3)1d zWy5-Sd-zGDeME^Ik9>%hjM}0^bW`+GtYWM~%pV&c+Y);hFA?t^Psf+WA1CT3+zBOd zFmXBYFIhWjND9eq$y>>{si7$)wITH=^*LQ9ZAlC1v*~}CA(?5JD?mlS07L-<7z4}z z)&iG+$G{gb7gz;s3U&j7;3#l0cpCf!m4jMAL!lr0k#G2DFa7eAEO`LjZC zVX!bt*dja^Yl%K_rg&Z|DGiiXNJpf1a&@_@oRC+_N94as6D6apP+qF7)U-NP-Kkzv z|7oSP)|yj0rM=dR>3wxV|6dS1Kv@w0007LkZQFK_*|u%lUfcFJH`}&t+qxNb>*sAX zw~g5r+xC2WzwL{+yW6krD6wPs4r0eSAP3L^m?xiHuZR!D z7vmCs27g6lBWe)ah$L~JEKLp~N%98yhpIyjrq)qm>Lp#29z?@THl{H%kzts#%xktd z+k_p;ZehdhEv_85oWr<-+)KU?--hRfVnSD8vET@=#gbxwF)kIA+Dn9VUd|_Xk=M!l zZ>9%%5${2uTHtlCV6b~|LGVZ@Tc~$vYDf!R31mvG`=H#Hc>3mFR>wUAXzL4B`>G4ry8UNrH-b4rrq>;zluNC z7k1{)08KD3UjP6B000Bc0I&cU0000000IC2009620000$04@Lk004Lae2z6z17QG0 zAMW%xE$&+3?hXy^?s@{wm~*7go5@<0wa<5cpo9Yo$SW)Zjv(N9)T^>QpKAUBUcd(b z0WVB+il`+O@M2m?Gsz=QeDlIJmt65iGre@v!+>no^iltgbK2GOJa9^_DIsOzhhUsw8 z5uAUJ9c-IkV~b|JPE5QrLpKXyk}j&N0DosT5CC`qV_;?gga6G8MhsX004PKOxB#p3 BJ$(QG diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 deleted file mode 100644 index 9fa211252080046a23b2449dbdced6abc2b0bb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44300 zcmV(qLaH4god-Bm<8i3y&NC1Rw>1dIum|RgzJoZ2Lrs zpu7QWyVk0GD*tRm1RDn#*n?jf3b-+JGsXb`o^K4<|9?_)Fopu#Ks7Vl-V09HrK0t1 z8~Zi}2F+TgDCMZDV{d4SjNq*5tBjvq-#O>6QvbMhde0G@=1>WT6AD?FYHu0ikega; z>#mApX-iw$(w6QH48JEw30FN{_sf5mTE?Y}D*r#_=EX+*uo1&#?f0LDsnA_;;~H3% zLxCTdVy;vtIwBs?ZoLX9$L7>X+VkW~9@$mBGp(v>Ob<@a910>RNex5OognF)o!ohs!So!2}}rZG)$IL^H=v$DKWnv|V>w-8hao zagH}G<;94Yj2XA;q^>=(%^d5(wx|WmmDKWTsi$hebmD*KGM53NIwPkx<@V<0<%C7b zQ3^@BU!oKcp8vnvoo~GfclBBJR-x#20u3VxJj}9%>0o@O93))a-xfrYnDq0!ZvFug z2s1C_1qdS{Adq{*5`qetJRqzDWxe|t4%kYf;$S)Id$m@mtr~kQIgrpbIo%ngDG9Rlp690_YS-ueT}jfMY{APPG@P%2ZPKjR9shqiV}7sVy`{ z0|v~by%6)`bN^R5>(}h9YWLPb5@~{z33et(!V?KjfUCMN+JyUgbh%bvyWiYeEilYv zi~`^ZS;_XKB%r!`_DxmpW=zm#clXua=#r zyBzKU6?hrq`2FqYh3EGz-A>NUzmpIT-6)K?&8GByd21|V|7bvg!|BpeQ1st7wQTh- zQdcdVvYfJt&avMWwy4fU>HOx+`yM_%esITg3*GE!fRiZVmevY}oC5z04;aqMhA1a; zL?6fzWl+*xE=q@(%PXC`>ngkGT$C>PuGS2 zZMmoLz0@IMc!&`)-1+7gPM72-eaBTw3Bd$mgjNV4gjN`nH#1**`<)+suX~vNnf1TB z?-~)&A|fJ6lqlsWCF0$$<@bLWLYYoFm#RV#0YwCT(`sH#fB6Slu3Fk^)pc*Gb)>IA zA-nI+4%<7Hwb-gv1XP@;u(M8*lcE1V4=X{;sOny%uTMRy_2PC! z7{p5Dv!l%*wV%8i(2MD6gJlN%4&434HC}YXtI+FlpM2Q4twt9{w4nYk-Ut6sX_!U( zf5p8!Pb^S%XdmFTu)gR}ULZPet=Kq%!{2oe>a8+P9c|k+c5U&T=RM7PKPX{+gg8WD zcvK@9+BEZA%{-(WIlKIIx9ZJzTCd^eDb97y@S?eA8A}MIL0DyBc>*xs@VLlRMZ$!V z*_w0VR}+_wyl`f46CWl~wnU<)8ZMIrq4CpItF2O_PJL~xq{TWP>h#qhIf|qKq5@Py zOf*ialDL3Mh$@ggs9p88P69INp;4&7&|YJ=&rEHqHF*oSItB5^TW5bbp6o(tNs-m%p#=hv(v3e?@xGt4L@*mnkUuN1rcwH9`shV5aEL7P2Qm0@9^aoCsw zXw0bi+yZXLdsnfDJzNC^5eL>TQI=m`1$~pl50)}o0j`}UaMwC-DDA5ZM2gtJv9`#F zEmGetQw|sTW>ag!tJvy=00=9g58EndtD<+y_eEf}SX1xjIGVj`iMKXRPy5W1U~3G^ zK4OeNuAEuF$*U%xo(=c5&?9-QZ@ScsXjc)?3YNPJJ>fl4(sS;}cGz$d$Bg)JSvi^a ziIc6L~Q{p3eaB%`>}#A@9Z*mFo8CfPSY^|77lWWN%)u*A;1STVU;>cpnu zg#4PI>d?IC=Hws;eZX{JR2G-x?XYB2chll@H7~lfYzJJf*Uer7RVb8gJ++DjE&!Kz z_LhqMui9$*((F6D+scmcfr4^bAjH$Xp|AI)_15ChduX}M3NNbF1(>g+1_CA(;B3!V-e!$D0dUfTrzVUEotZ~*77 z>|yGpeoF{UPMy^44)+;PQrG@$-5j5*y6yzAt|d*6PQpNrAcPW&z-~Uru8;d>X{2aj zbXZ3}*WZZK?O&mt_A3m6Vu!btFb(R(Z-odMIM z(19nDmri#pXLuC#A%lZqHMQG+q}94|-N&;sq;a~GPUoXiay~M}=Oa>dK0Jk0)~RTh zc$oqS%BYH^!pN`H%L`NlH*0*K$mqmhSi;1$=K|{J`-}xT*!zuo)f@*$Ri!9^HE|v? zTP4vdk5Xy}1F4tJ(GL(YvO3O3t8J~d;bUQT1&3$9Kb=Xk(a{~U{5UG?unZZUc}{gQQsqJ61_3;8oGz zvwSBh-0e7KY~}sLDgSns*y?FkAyix=GRR92d0OozDk{~fK8&zUarRT!-)PzJuIAaP zM6Z(7R7;LjRYW8z-l0?xP+|C<6`L&&hL&ADqkcPyxwG_ginOiU3u2(cUDMCBWtQNtVMIvbWf`JE}N2#&>_ zJX#qhD>w~f#fT)CcSGx13LX$S+8B;38K9WoT2s(I)941yT%WikbWo99ImmQBV ztE(#dY?UpBMvv@HP)Np)4g@^W5Ea0~LLIJs+nSY7eEL0gY}I}zJAS|0&G_W zU8kF!I2(?}NgFWyTcpJBfauVXI_%_>c)4u?!-d>pO=s~(@5Rx1A)_7DULSYbmP72$Zvs)fbSr%m**3Yt(l?H!! zu$CN_mimVx3RHE7Z=i+J)6vMAvgjO!ilJInGtnM^Fq8e0t6`KzBe1>bPDU_W$~aCR zDe*)y8pJ55dq?{KGKpcs+n0&dLm43QSt@4j)(`zog*BoqnO+?dQ7?dfS6jm_S8-Z; zeiYw@B;R-7XN+cjO5M9bji6Y5;?dE*q_e(gA7MI|LK!5dY{%FmCCN-Ci${#(~c;tbMD&yxPU;C8R}K8q zJ&wdifFbqb;e!DaOw-Y$X(xxc=ABVv|2C|f=D_{Hm+iVJb+$~05@+%B;Mt`$TRO?y z(P+~_G#kvN>9tU4Cr54RJRb*;2^FfF-{5dDXWT<}gXXGCn-TQikijC_u^yq!+8u-u z!NF(Ir3wplRSpV)zB7V#;*u^Mf&0332w=lhbRa&0@$B83+sYbK?5FQ*ok=#k=||Qm z2gZsJC(v1#rgZc z19f{^wZtKbAT59cyQ?ArtYY{P@NW2`%LCvz@%ki1M4e8xgg%6?$IIh>$`chl2kM@C z9SUic=t4ZUk39qBJfJ#&5?6jD+g|#8dZ6Qt5YH8V&6U-1>f?y#8LIUeyTc8~-(*&V z_Xch(({a1Q{u8Ocm^?=%G5R|5XsIeeWUp;ONWjEWFlCV)>JC&Rd${j;#*q@LzcmM^ z&+-gR6)90fgb(xOdH|QU9!%~QtRKMOTz*O;rOsp~w(Ye*QEH0tldl4bK7EI%UpmL5 z>|oM?RoYutouF2q8;1=#f_Kp*I0EiAutdUP>N(Edar6z<_2^itR<^RFGeq)@fAAw{ zjy4j-_!$BuvC$EqP7pkxWZ6$_Jpye`Jr$s+qb^eYfdtV7dG zCqa0s`U+IJ_r*1OUR=_oa_wd#2nmv_T##B2*ybQndTDe}mMVOqfD>LO?%23Qr=+W* zARrGSEg*=GWGs4t^*mq>*%E0-uU*(yzDfRZoT==)pNQQ&%Qy!HOIBNtk(+0kV%6i8 zW3r#wt9f*9x?2_b&cX^qQ9hgx6haH=A5jQ%kxDozvxTLGz(_SU0(_L|R8c|Wc~vIt zCBnhsc*Oy2c3sG&z}B*;_m-7L{Imu7Y88qg!s$TsNN#x$oq}{&X_S_JU#Q3zWb255 zyx6?fjw57$^Kwr8o-5i%2zV81-8A;IwGq7UKmQ7Qy-PplG13YvBF}1CwaW$#H%;D9 z|M8O|TkMDSBlX)8sCJyO!4~IBX!VzI>8b^)haoSpsi9&@tD^2Lh zjp;dMoTN7CY|BoV)KhiW9EotZuXA~1V6Z{j8MTN;_ym&(X5bPJctim|Y8yw4H=hkQ zoa+@aATev1c(O$tg?l`XTbiV?4}m$vG?mf!l+6a~vTm2rYd02+@b)Q^yx{`;GgK)f zbetX=D5(*%n*vAk-VV}CQZZDX|0t&P`fWrI?Jbq}5>#J<7)@RMp5BhoqO>1EfQ^^_ zEB0RMCVI{^M!X(U-1|)=E<5S8Q9mm_)-pJZyP+n6GW3FteIiS1~Uy`1(4k>UP4MK_f6xnc}9F!LN?3W zszgNPMSPo|C~*2T!lNOsvFxV-(csidQ9hNA;rMlgq0`~on?7nC*|hyVFqU-N{!trN zb=SKh8opbyJPiF&U80?10+Z-j&r$~Ah7aB`0{wLiE>Xu#ZyObtMcVe?7t&MiU(NMM zEvs4%^jb+kJA#Z+3p5&3K=b-a5Un-T+;7Y|#5{}!Xs_OBnDkjNvl?>%{~cC1oVtja5cJ> zvfF$UXfN6T%8n|(Q)=!EFuf(Zm7+e2Un_N4SV?6*lB2Mo3@35kY`jQh=Cu;fbd}}M z>cI*6$h2_gep`7^G-Ua8{LX*M(K95hi9VAvCvAw~Ir3q6Jn;yAV#d|vtf zKTA|RQr0~Byh1P2wE1n!vcZ0rJ@p|7Ukh8rqMXw_1|=I7$NQmWQLC%Kod8r;=+Eg# zj4603+$d62>wbpcJ2OFIpRmi(|At1y6Ch=` zWixz6#Up*Ry4F<~z6UPC4_h!Nic6jQHa}35l>Ny^r|}A0EdjuN1OF+g;!X$?)#eMf zv2i;%`g#17iyxX)ML!GlGsk9UJ@+FT;)qn#a~l*AE2rVo$s#oG8SV(9g~c&a9C8cQ z*0D$iAsICl!qIDIdGT0LLIcH&NN&Qu(O@0lS)zpiPx8P^zP0os7i7AjfP?D`N^F&H1`6~fV&Ya-zEdJ?xR%)rTtI_eQ!Y=>n{<>VB0>C`(xi1kup)<*g!{n7ztmjYOjo&h&;)MoHjZT^8w>!pEaJ3VkAbB;h# zAM~aTCUHHl))b}WX#k*Jy5x1rc1q?1Uy5lMGPoBhX!8}`2X3#nlYk_xkCM8z2lS}i z;kAxeiv=n{2(hrNm*|t3k9$s)8twAz=ea6RtFqlx@_19-I8kMY6LrfTzXlZ55HLdjAaym*Aj=%}JQ(7N zdQgnOkg$a9VUA*I+(=oQl}egbZ?PU>n$YB@yZgc6(eZ8XcwifV=~N&`r1qY_Su`!&wF9kjcN0wax&z1<&Joo z&relZLOg!Mag!nD4m~#`4S_U1@x7d%s3T@=pwBkCmg#7sEQnD$_StN0G7+1OIxLIj zL1m0wX6xFHs0$Vd4~oKheXxPioGi*qRxL-W4!?!Z$?`nl5lEBPb;9wp8wz>}<7iOG zRaXAc-`DabkCRG;_Q{A(3r_2SE_FUs-gQz_&p4)GaC0R$v; zHW#pB1a&xQY4*-=596p><>FFSBB%9o$VeRYW;wY8&`=ey_p2?^xv8h>5# ziS$0$L(h>iH1g7(Rr9!phk2T^D5!Ysv=JVFMiQhTmWT7FdoE^bg{`WrA-0?bCguCc z)+&pA%)jT$mfOQ(7gFT*egSH4h0|ZQQY9Lr!z&JT*a_Y7EBckGLe6UQe+jaEwypeu zDuDQMmNJi-z^bXy=v7d;5SP=;~;mYReD|mCa-PFO`W**hXnrDuM*9z=44a_wHrYwmCv;h zitB=~4JwR(%a+>iWj3Rle3r@5^r~TLr*-OXbErAanzU%(P|^MH<1kI7O9g=>yu%nW zgCXqo1=ZU0y`eMz83Ni9W(=;PkJ!; zhb?T9Ta3A#^SIV0afQW}M?3{Ew#k#l$v~b&yMZ9bc#O>Bq{9xS`zCZMd1F(~@;(?3 zVKk>|Y=5;cIXE;Z0^Y5HN%Y>wBOD5&_z_M9qv=fhBB=u3lP4{Ct^ottBbzSgCzIfC zfW+r2s34YTemf(+`c+S*;?6l+FEz1W< zNDp!E$-T0U0*_V&gX4 z=-L!+9~!B)F?q!>A-FPbHrH^p!MV9G_5;P*e=lDo+agKa!fn~vC5?Y^zu`r$(JO-$ zmQoWG^qR*d%$*=Tv&BJs2WD?Ymo4oE7k*`@O)B|yVQm)S$N0i9(%#t9Z9P=k&+cGD z@BL5iHsVt=*(vcvI0$Vpv=5_gbhO7lPrC={OLZJz2ze}MOC=#C$OT_G0hqXS5n!b2 znbLpsNsyBLrMJa`4z^;u07}7Unp=Vme+gOMp*qP+B74E86-sGtola0xF`6amcPREL zCW*U4I7Jj9DtX&=M84-(+av=t+jZTS_9+tx86GZ~+WSGAfm!P#Mzon3;r9ug8DG+% zO|1WI*de|r=HL1sWmLB#l6}pP^{a0(!3M|Ow^$*NgiN*&LFsP4{rKm|(g=;L?ZWSp zS$;v%5y7d(GKe40io^!jPlbIE0-@bx*u~ROUJD$@Q;E7`>~_3?#XLSs`K1k1qm># zdoR$x-ne2(rk_STcg1yAQj9e70T#Tm0yet%VBCBB<4|9pCMLfo*_YyuG>rb^T96V) zA;B6EWyyk84kglED?HAQif4q$V@c|R4eX3JnB!o!ao4=@GV2XGjfI;*rblgiZq2zK zJM3<#gfl(LTqkxh)nous7HvNtmNV=z&kBeIcP>Y+dkWk}9m9x}O&^-vlLYGfwZIlT zBFDn4o8to0Hq$BF%0Jpc!(a_^zUJ0$*{Rc{`qVl#s@u+XkzdSDNo7kYu3w`|*{9)| zWJ|+OlOrB_j2!92qR68W{;7vU4x+=e$(rLQiH@vICkPpw7Nd5}hrCnu8YbZxCD-~IWP+V_2@NeOsD;HUl1jS1$S>nc8y-M5d zq^x3o%BJCYL(@lBoOqNooY=7rJmjzw{{7wg2mkiR{^H;M@vr~ncP}31E8XHgUVQmI zz0xH&yZnkLZu8@w_qzA|5>I{NT|VKBp84M2_`!?cb834V`aGH5+4z_Bk18sl=D6NkS?9kh(F^T!w|)D@@6}#s8^LgHaVR87VGv zoiI2E&MaArAB~#P8fUrQKPsllRKMTV)ng;cEi9He8YH_KViME6C`T_rc{1&+7wao; zAY+b#0IoHEM;QdBA!im$Hv5?<>yObp=zt}E&1-X+qEc7}X@?H>IzN#umx=3V+C4bz znzd%Kh}I>@ZKWCKk-lQsL9%SghbSMU_sg^YS>q+8iQnv5dX&s{plBtaOj9CFO@Xu|?- zI^ydEBRye*MekXZpRrI6Y%_x259?fL4eAm`RGiK-hnACsKBjI$fUMmHoI%ZhW;X#D zkNl1>+lYO{TUZRB6e789#9Cw|sfE~pj_nnDNhoDgX_oVrlpqs*EP2U>o73UpfB2p! zPeA!O@UmZ-dd+qCaDW*wk$7bro*W;_bJ_e5cFQX#6J?R8#Cjj0ar#$&)?D63RpB1B7SDc7-^~ud0rNG zJg#Q4**a;xhYSf*ybNPp$MD3P``44bCs(^uie#SEinLjU38;mLnjD3(2b?%<60~j; z4krsIT{td)z1EGEc^2A8Kso;}xqx08yKGKQtEX5?ZnpFp zN$WmtXw7tMr#+_@a?APUPkCQkC%JuL*INu0@Gs}GS zz~WHW=|qzw3*eNxPY_s&oH~2=&;?vNK)71VB}~&Cm^e zkvUey1JZQbQ09`KjB7Wvp(=5G>yr@znJ*NzPHngivxy~=ecYT5!LgeW0sd%D?mKCV z7hGS#fxnb%XM}m+(VY;P2D?}>A;7&FB)-hfM@;liNfkNVk)Lmj1={Eq4fz22)WMFy zVnh1y$8BB#T3W}UCvT9HlHrT^=a)6Z15}lGFv}1dT=XWZkVy0si{*%1QZQRl4_~aj zm+h2x+z^C6Jm-_PSTs2oglg*b=)tZP(vpt!j;{nRR32-KC1M0CcByya@=0*w|Cw0tXGc(ypyyfDb&??i;x=3A&8EPcL z5)wYiMWLe=v9LK_$`nG$OZ7cA4Z(#lS2iJJEK06w`&%_D3Y@YjsS0R`XJbRL7Ck2M zH zur6XsRqqatNcGga1;{^^P5vee7SfpNAq&h~X}W;Ri;5A6O~zrANM|BMS+Im2@BP+D z%ZMYojQZl)*7$p@=x31u7TD>kSHTcX1fm$zL?TB71ZR;TBx>x$dlLQ^kn~fl?-aF! z`E8hMt$~wXyEy6RDaS(FBLG@!ng#^O84)odnPHcZ^_)!BI-*BRYOjKCP{%8YUnXL#(bEhEVjVocy0+$4giL%QWNz z#)fD@_-w19Iq3pIB84<`f3V-6S+I-Emy1vkS zed}i5k}mAseHYHBVpc%{1(;!(z37Z7N<+djmc&Afvu0nv+AjdaIOza@o&-|KB%6GS zA@rkSsrT&41-|ivJ@&?iOy&J^`8fPlo2$N{o~$1&`iq;}S-qy;hSfRd9n$|K4c}af zOF`DfED@PVX5m%q9-m^r`2Xx*=YK(+sg6<0)Ra0(9jT5`hpWR>S5ynC4^ymCHF^c)C{AK=P{n>mmEh{mh`is8199a%S zfSvFGyay|w18rzQ6B!4uGX942gqnz7i52+=tN=U}CS{NcEmW3eck3;9Mk3GH9KuP1!-`d} zx$CY=?z?ZcJuDOWGM>L&@Or#MdI7~7ctME7pOB;GAqC?f44C*QGhx0J5o3acny|+l z2S_hLbmHZ(bGiu$o)-hGjQ2Wn>h!U(O+zeeeG ziDKx%ycH?=7%cY*IOIjD1Eb_MNa5v-;KiYZx5kjc^2Yg+5;bChK7={3$*TvhCZE6y z?*5R>n^9si6CoY|O6s6l))<3=IW<1O#kc}!`5AC(WX^3(Wf&i#vP0_<6WahPQRnNH zz9#n;l&SX{N2vc(#W(M&VLSLhhmue#o-O7!X>2JaUN|B^pdN+Wmh7;qrK)r1a!t!d z%OnsWWA_40VNj`>U= z*{9D-O=LDvP0prTJVvwO+n8uGFxu1*_`1QxCC|UVTWe($8OWV-`C;tqOmJ3ct~3%S zwaUcb1o5*=qFfC-NAYB0Qx*m%&8c=iX7dXK}>+m=5jZ!RE}EoCX9FBMT*GXyiG} zy+^c&-{8TUY2`2gP{N-m(UnKtIY#18WRXM`U+*LI$a&7$m$*^S$f{&#)HcL>VuJ`q zDKEPqUPNsHBV5RVRINrM-3*^0I4~qHW@XKi^{z>UmJAK(^Jef!FDzx0{;qYKd*{Ei z**UiBlrp#v9PZ7$8to!xjNm?y z#=##A>CYm`E^Wp{dPD}vfc2P9hqDTfJjva+m;t!eKRpwvGCot!u2oUb2{n^1{3NNn z5HqtNYqoX8ZQ1FDt;FH_l~Xc^Qkm164d~i!`G#If!_k=PQyv*$mK~C*xkOWK$V+}B zorCnUWoP53UHoK_s!FL1+)?1>&fSMoVgP8BYY`x<6q+Uv?vpyPFV~}D?EK`@1|2Ts z;&V?2oWENNn+zr@D;X@@@bX)Vq@%gHT;m-xf~8l9h9_>5&_|@Tk@}qU7uIAD)IzZ&o1q-=^)TEI%%J9$*>f|0sH189)7Y>Jz zD!*4~@fIf3jABrks&;$>2nE_XOyp%P7X~=%4y;6=jr&uc)$!Wq7*n1?XPj-{-5MDg z5oCD8)sqKP+3+MpRG~h82sg6g@sKN!BFSB>3B;gsjAR$TP}IcO-%Zqt!(OX4!k)?` z-@=Ba6?hb)fqQYSzYz~BkxN?!5q7joL52-Jt#8(cdq-;B3_F3fDs8XJRqGHjR>c9U z|7v-l)LF^5Fjm<55S1Mc1N;?H#+jsPwPws3b3{cJ!Hr!+AZfu#sG_Z6hC{rCG91N+ z0yUQNuSui4@1m*?<(UzlOZJ53mW+7xvn_ln8tI0WqTzM)h*SjC*JqVPg*yYr%KQLk zJzRT6mY&L0y?cL>gDOt$HGZ~VKcct-o=uB@a>{y?u0|U=ew0-TM?+GQl?<^3Zt#0_ z7q?rBnXquJ5tY_i=Nc+^l56iEbe5>`9U+ld32*XRk+J1dfx?Y%wpqeg2{z`lSg23ex^!%#s?!GAnIq(Lw5*4Z7H^EPg4A;38F1p3J`y?kX~zJ;h>^kctt(g zvrrNZ=CyuxXIv>)rC-fngI)PqFpdxz#XP~cH-d_z@>&W@jkb``gAV3kXG=Dw=_vz9 zZ7jic4})4A!B7mDbMQqNW_;#;d3K4X^*XoPpRWl|pagH<#q)eQ6f>3?a-(E{c`L^@ zeTZJoC_Ax-cE`R)J%WN;JPVG3j=qu6?%2V>?74YwRxuGlfwYJsFx6WOK1OuW=HxIZ z!gCv{qA%KUC4<&Dr{1k$Wm@aeb97!3QQk6@v>S|xrXR=VJUDPZU?E8&JeG-MLVY_e zKJ=ilBfVh~5tBvViC%z(%+&J))`*(`v{c19;yP__*t_vFqMhg2R>?^w;F}}Mm!gcu zBmqX|gcqQ7xB^O{)Tq#rZwlmgZvJJrbp|T?!v{lN=)|ltVn?M*^q53^!-u9;Y{Tj- zvyy?zG0(c<0FR|t<=~aeDA9)GIsT`!^14{9S=KxvHlBLQM&{DLXEp%S{XqOv+ z3&?kYq6e?!aWDMkm*l~L90;MR#(?`~ag8ZHp}Rt~Vo*a7_t8#khfML8F6cCKVi|m} zx0%vHr^L{vo6HWE<1kGzft_#Bah@0h+IS8ARG#k1rb#AMvD7WO_&SjU-cWqBqGMYC zH#FWYxz)Q^Vb-lpV`}beCQQ&3=JVU z(QY<<(cxiaE%4v>o$`a8$}c}TD;}M0+h|Jx1d%TkoYp@Xz%5oj^_`cvI9DFPlAKeP z;ZC}0eD_VF94VFQp681>|0m~(C0C5Agop7Q36!t@tK$o42Uh5WR$xo<)BQMSAP@v3 zE!o^^A_aVM8FdN*oJK30!%oww1E2X&aJyzVesU_pwLMEZ$JUYE7h&qARSjfeh@6HD z_I*ysIBH~PK;H?G1WzV;j5U#vn8S2MC5%lbI^IJ$Tz^sY7(?luiIh*~} zRm8;18%=XpSC#xcUM85I>&>zcVdeQ{t`JqZk|UY~0YSpH*<54$w@;?xZaWR(2t##5 z?ST;km9Rm8$_>B-#Ol&++g+n<@d=X1o(&iG(SNq6y8fe;_Aw3uu z5?O*i+$1!Mg$x;_+3AkD-f&%WuO%X}XJI8EQxx4xAvR<|>+)eEi~VA)L}$VL&c5i; zbI4}n&~~|K4XboR>8OJN8YIazy$Z1Q0#6AVEikTKi;TTu^qZK+b2fw2`u3B4cn)`S z21dx%>I4^%-`cj`zqQy_8u(Rt8Z)Xvg@K~)ec+n6iR*i+NCuXNsZ6*)InxdXCgrq&r&U@x zHHgbWwKOuX3kBhIc#&x*B(jA`F-t+YCAqhb>}&5t^rD`JwQmE|@vj2aKD$FJoD1dZ`dF(VW+itjz$JeQo7^(R@P_JpSvJ`o)D{wmEp1IlR zb)hj(+qKnvH=(kCp-hxorT*Y#oafM#R1)RwFk}HXO$m8y$sVKp*&KhSdGg=AEEKUE z1um(aw;A=&t(jTR*q=Usqj5G0-k*M%%?I zRg!8Y+sTN?>xG!J7$ckV`1_tc9lM_OM-4!G1N7OhXypv%%DLd_M)F7b2-1vM4#$WR z)nIMS37clL-e@O4>NO%;YAX|7BM7E01D2?FBX*w1v7M-`BWwKRG_8hR6M<+OmG>i& zh+bNFDYm%WT_#t9%Jk34(PEUk!e+dYgEgTJu8Y;W(?%1zdpF$xr}j1;BFn`(sGRz~ z4$7ZSwL2Mq1M|SC_};n!ONYpgFqL#S;0HICtpT1$+m9}Z=&Ob4amp{RZHtc6t04wn z7YJW(@$|F!%yZd}mSaur{t|n02tC$VAVu!AKif<3%z38}HSBZ|K)Aru z7Le1aT%`)>$V+2Ds+FMKw~vsJ&;Mk&c^LKP&Qa)5_+oZ(v=gRw{d4e9~7gqC;o>5>LC%)%II@g0hACrYboe z>X))#ci5Kdja7A@P$EuZZE5P{O7IxwJV@7CZ>l2P@v6+yygk`<>71%glj?W>bjgDj zia}hL8*I~0`V{A%kUL71tQ+vR=h6*hF=_;X-SzZ#J8t(G^lil=fKWY|CFad6YYTk|p#z~PUi>8ZJSEEcKMTzgAb z%=|D(c8I4d%2}gb@N<}QpwnDtkeZ~PN)S}Y?l4o*ZO5`DRS7fpu|>z~CF9Swj)|+y zMjx;6?r2uw{%%(;*siEJ)n=W-;pXmVCR$9|^w3dfO7TxuA$OCOCiBlz%5{}v2n!(u ziVOt)-s+~3#KVJ1Qzxex;K{_elQ!wJCrO&2KRso-iH+370hb0qE}z+O`--3Oa|x( z*j)#W=!KI-pjP1Pqww1K5V74tt%&SuM!Z%ERhVX~LMVaWHsoSzvPgqsqI0w6bSj;r zZz+XT4yeSnqP`dUuDBGxZH-Iw5E#kXNcc+TDlqCBL37N?SzIqThjNSixD7KO6Phhv z53oUf-yTQDdHR`covILW_*5D^dqzFazS(m*GW3+?9+}rfq2&u5HXeo5)L!f*Fk_Yka%AAL;&p*AQ~$jy@wH?zO54wbo%8x^i-BH< z*mJ+_8IN}_g4R_u2>hH>xiW^;G-$@#;x!onYEg8|@Ls0&p>vEzt2^~N*ggk@$GXG(BJn1& z=XP*@7zrFr(@S`;on;e4Za%C8qJRPx93V8^<{0RJcpzPOl+K!RuZ5}03q=4ne14Vy zuAIFIbJdOaxDSd>$UjIUV)6v=pUPRBzrq-%Ua| z&2AS~m9tL6F}Xyfijs0G8nPqK6C9{=#g!#*b$M1k7^wj2rJPfFn=>%($zfiDcs;J9 z&6K@Fe6D<;_9iP-OD-XtT`6zY3?$c{9}a6}9wr5m0u~7dNwA_hIGivLwvb$BaDoMB zaE59j-H9Z<60bbE zYcVn*H`d~3+jrSLeSuA79mg^;)kv}-vvHzZ-tnxp+KPGkz~^kY^38dQQ}mzVpAfGv zz?X1r5iqu&fUk{<^DrQnBy=*fOQvr{n9LN9 zAjOD4f}j58N#?+D`UZFr3zmgI6{?nvFPL@#{=>OoV4;m(qAknxa9V8%4{*kIAf`Y! z2lq%BNabvRZfGB`Wu^5uT_r5=44biTBBPln_V>eNJ235W-}Rl@gfZG9Weog+#@T%e zb&u5U#3eM*gn0PxV@vf~J^cr#$UI1GgoE@k0pa{o5i&2?_4L|`AyB)b9s=o#>3A%8 z3Z)Kaqz{_yRI)sDjVyPXcxDsu8u!6ZQ+A2ZW-et+9a5zXG@30TTVoE)D?M#+Mn6Bk-B~xkM zx@jFEZ0oRNv~i@ES_R@!-f{p$(Rwg1!;J~u`52k;IRe^dh+lgS30B%5`wTL`t-p2bbGSGX$ zB1+;X${@sw*$q{Iq;uv0AbdzU_9&m0f*_0rgXoovy9kEfw<({7@oU;E;7O!j)jF#7 z@)*bQp{KEsEz=GItvK-n)(8P*OnQLd>PpJ(I{q9mKFIu*jR)nDl#kSFV)=lO`c9s| zLF^h?0Ri|xXG!JlP36X3NV0HxG+Yq@`N#@PP(c^t1g0Al%fjG7H5@zD(Tpk9Kyi+~ z;0v+|!6!7)m&j?Sb}0ZrkWBe`6+IHf zN485}Zm4hAtrri>28&MoEC2lHzXh`~yj;2-q+y5XKMZ6T_;=XCOvg>)&z@Tb@^LR& z$U*=5a&!A;;mS;*E$L2xMB$szLPOy_ELHv~t>4h+ULMuCS08dZYp1hvhx;p4Xh}pM zSsKQH^wClcK3XrvH=-X5$x!yyN8@?h+)PAuW^th{9BFHr7y8%=&wpFCC{Fj5XtYI^06aj$ zzan1`;>^_y)=1*DB>dWaC|O6-Itf(SfJooDW|Eg#BN+Cs6S49v4FphO5&19_G6QfJ}Uo?Ae)un^!B&l4r3j zCI2R5GITlXY{{|{R%&5sPJi>V7Ej;xC&xp^x}oz28skSFi2LVuxOucbW9x7+(_~yT zt`3a_k{q>g7|$6E|I+^V&oQi5rA4!dy!qsW6YN_|gXL7fm6nmM9|D(bx09dr>4g12 zJTVq^?RjeG;Eb%EKr~ArVXO=vYWhF;JqiaIl4y?zp0)VZ)Okd0(BW&IAuiYe7K%(A zlkgOI?QfFQ#R{p5*^-YjNao(0YR~>7r#^W*-}$=w>k>pSy8S zB`+13in3N6J5CA&TA&*Wt(somOfuw(ybe6i8TQ*$ha9v16nt&oJiH7i7|4>jnYE_9 zcV!4_gy6YXh*dLjLo(D0g7rC+>*nD9Jvaen^F&JifTmWXtH!zhg)(GSh#s#hQ(p*Y z2dIyhR}W^r3>(xN<1UgH9!KW`Y^-s9P7hR;l#TS7*y|h_7$Vb_F(Ep+BVdbUCVJtu zS))e=Lh0{!HPqLMCsx%>FtVidm7)_HoGAKeWeI2}%1s9jBasgA(}w_Rr~3vLA6{q+ zp&8RE2@Aa>&pDb<5UBz+v6*Or5pCej6GQQ8c1yO15%`U^NEi@O&d~bieFzBZC=v|+ znk2$Pq^xyR4_khMheN8(mU8r){Hi+-UQ80`R41Ceo*0(|l@N6eDxwC?@4iU7F|tRA z>c}oor4=&57YNz9YdsH3Zsw12rGeOT(E7RRsVX+1;UpXChZI*}Xm<1@8y zpYgXx_?1gLlwC8`lU%>`(s=UVF(W#40Y9TUlcbH>HSL5KlZ}Vy;cBT4kbRP?KLC}X zUfS*ZY3*3R&r0&`D9xQ0cfod( z(iOs>BLNGGySU$w#l)!~u8C(MJjVv8ps^!Wu8rgg=gcTQOa#aP_fh`KaIjhgXpl$d zJz}c3Nz>^O0|Ev~NwCa53ecOxWpaEs(%Rej?k7=&bm_bV3bt*gt*wYOJe+)rIA!KY z5MJnT`cG=$Pw5Cfm&Eua;(#S&amkVeR5**`dgrai_u+9eE76Ikk=N2%A37@J26vJw74snDcfdts?q@V8A&H?Oqf8s)0LJx=jdRr#VcaTyNu9x668<{?~i~+Kj4Jw=2GrRs`U(k!L zleTfgC4t2+z0tSnE8;Qp;ICVcAA(lzFaMyyQ%_vs`uULHBsxe1)ou|hs5q6cMBStz zux5R2nk5b*7Q%#+mNnrwFKM4`KL(6(dAp?_F{hIq;jPibe;+z7e69C-Nf$yge%Gx!Q;4oR+i6z9IO56#jYmJg~w!tXYOtAhn>- zS~j85N})+EoZrsj~8n$!+DDDJVAePvNww!1=AaL_k2Pv ziCd~QAoOL^6VYZ&vLjAs!2Ad>GWpciq>L)a9q-K`f?{iv)A$lwgtA7Fg^t3gMHkp8 zo_rj0GHzWf&4)UH9(HTMdWsP6Kr<)B-fV5P`l+;xWTmbVHgQD)t~Xd%Jfk^7m9XG; zG~I$i8WzJu0zTgf@Iu+$OhbZ4XeQNsFA-%m4U$BWWwyyeEGBoqp_yH}%<8NQ-)gCS zqLQ>B+srDU?rcQl1PJY>FiglXg5H!SH}nz>2N`NdX|6mh?NXl?Ff0VyW_ zdsP)rXV#Lb^lkcd9wBG7$*du7^k?4>YJ6Uc=~|1C^{T6hc3q5lf~I3e-s$4-m!|6h zI71nqgkIgij-CHl=OR-pqXUs|uR)D1d7Eg(Cb&iYu_^AmcYJhmYK%Vh@F4q08=pft8G&9YAcV|wiaBHc6l?^rmVX@T)B<|6>cmKOLf zhcGBj4&yf4w{1u8K`_nrgnX3WBX*x{ui|s+@nqN+(pno=?76u($(Wl9CT7r4VL=2t zs{YzB$W3iP;E(W%Gmu?Ob0>_Y{XFlZ z0lKTm64t#Ff&hZ$r}WzlGCvD!_YtIEsK29(8UG^ihwx_jrs&)MUxQLc$)G!v76Mgr zO_40r!46|^rebORQr|qkIuDa1`*xM>IHuj(sgG{|_Ff+8jpFK-mx)wR4`rMU@{ z-TEZ_g1q+}o3-WWsP~W;3uc4(!cC+}B0khoPm!l!8HuP4W(<3z&%vt0-!50B;pd@; zY7ih4z%E>5VD!-W)9^zbm+*Ew4(!zI8(8ZiwMU8-jxKY%QvG)F6DWW8zPCu|K6MpM zqNnw@M=@K&{_^Gzwb)Z8GSp*%am3gxnPH7i;BDZMLQg)bk$uk%sM$zngm9)=s~d8C zCTh50uGtAIopRtn`#zG3J)|#GgABsTyne3NQVk3H#SSB`O?x9rIe?R^U`}?d|}2o z!`pipFNdbr4xDfaL1lw;W^Hmqj_JAs)4Y6BYpCMfJ>JbM64gpmgk+It~1 zv~c!&P>U#U8jgWw#i?+FyuxOPvh0(X^(VaFan}=qxv>gWB?HQeHzn8dL)5U_mgK8| zb}!WW7uIvQ?j)MEgPJyV+TJvc#W!(ruza1@3S^ZS$O}#b z>C2in`#NyTPg*RQ;*nxDuBxJ0tD-Dt%7Uf@FsHERTB`?nMxN8BLp5QD+x!NBxI#?3 z&3Y{ol#?eP6wvj|?$ZV&^pik#Hye9qkY^^RmIz~GxgO1hgQLAe$n9L0T_j(Ac~6&} zR$IPl(9LhTHh|m-LEu!tW+13R3n6p7ApuRZRliSazh1XiR{f{xq2i=qx@0AeRo(hZ z3e!N%pYN1;Ux{~9PM9De0?N=&wrXH`CY*y0MTvUQmOVSd?y>(RGJ>JyeL@btxn*Hg$DY&;|YGl;?IA+Vu6z{6{bmriLYpTh& zA2wJIeMEMRmzp1_<%>15uXkzZ=ee)`6$#yIz>cgkdGef{pXzx5nYxW% zV3RvGWeOYvHV_SCkS+0+@ZS3`?B-AN#M7?b$xL?_uN^H1zl7}O&t=~1K?D8TUV?bT zRf6>8V-g>2H*T98y&c8w%gI!lD{JJy8C1J4ohfyQVKM5|yXsJLO2(!3x0tRjCK@fW zA0F>_$=E&{Y3@YPkRPH+F>Wj;DSRi7O zwXEip1<7`=t1OOUQ6@t8#*r5yC`RMlX%Juq;!>dF3Hpt zGtN%>p$E!KcaxKv@x14M2d{i*dT4(}0_%scN+o=DmH7)D^XON}c<`;f(AADu+2Ij3 z8{V0glW%XaZCiqW0@$2^*q@rv`ECfm9463B2amlMrK5mM9%$Fhx9OpMAMoV|-Z#;- zVO3|nS0$lkYn%RZl&+G`HIm=vFTi0V>lFec8L@?JO5=`(GEKWm(mleOMSU&@?XMGG z&y>7(j7+17KDs!|O%5HEy@IjiIfX|3SCc?0r11<3W*H;PtaIh1&PyP_{-}mOzVJ;r zgq*@`{8zFL(q!t%pH9QH**M$W8F}xB0)Wl<>C{j}we!B55Hjj;nGlff>0--%)UlnA~G!b_e2Kfo7%a8u8|?? z^~Q(;nyv&wR$auw3zQR89i>c)p*n|ux&*25vsEThVuT2LB}(cZEoyGcO~yg!abO<9 z_u7vT#eF>G&b$n*u8@WsOUZc|Sv!3Btw%&SD!=I!5w3^)=2+=RNvKZ=5PiK|wQ$tb ztHZBE{XQb5T^FZr+8L94uvFm14h|I$NTE!+@q1f@i0!!-vyh>qos!)V!n(_MFz;NC z2UWGE>o=KHE6S)#N6*dwo;VD{5*eLU1GDR4VEpOpK-iMU#h_3NcqpejT+jHzZOac5 z@(c8XDl83>9+Dd`f4mvfeb4KP@i<~>M2{22o1j#^10yYBW{iF^8XX{Ck^v3OcnOtI zqk3~Y_m@(|vsuzHp9CtwKu1&Nb2q-Vzt3XCgPzgRMfbzGG*_rP>U1Vwk5b?Js`oYf zAjmd?3D&gJex~jZauZo-FE*Nr?qW()sV&h2=Y~kLxge9U2_nS~_NFF!jHo1Q9}UZP zRB?kf9t{I%aqzrYeM^C4st=eiu7;HpWwy)hu~=1sal%Fud)(!0!=i$jSYj}61XZa% zgVu!$mAxJs+HE{&5^^I^$z7zjRk8ipGE*qLA)1&0-9W5jiC-KQIAr6T6I&5yjcwY8 zrknqn3*PIhWS{2ed&l<-Aa~@45xVm+W*gi;>=btK#Pi>j?JH3n z90h9x;HLQ+S|4S01Yt5ydrteAETBBrwkI%)lZezeiT^M{whhxt`g)4MBkNmG-~x26 z$FC8hskrOX86gW&cN0A|-J#a#etBGV@`3R?t*p+|?;Zn9wPOqWO^(6kEIF4!+y(~q zTh7*nPpmG85*gR}xGOoilAI;++>py|<4#k;-E|=x!5!5Ecs`WDB(e`)6a^KK4Z?(x zi=>iEL0nDaPHHvkdDKo->2gf|Q|v3=@IqzD3F=juZUp&!cRp;zXj9N{&f;xjveyj} z)wf6JMdRg(FHga{3vUe@FIxjgPsiUF(*9q{-7KRI488qa4 zKsEIb$Lqx-l5oeULf6CQs>$e3s*zVFG*7qfA*%YT#I05XVH2<}Z}S|3?bATTM|q;j zjddfqz>F<$X2o+?24*f7*c51GqQ=Ol^Q3XOq=u#%T|&$RYH$gt36(@WC;-5ix>2O6 z3D!)EOD)A%Z5Vd(Z=MHxG)Zvu81YV8o>l$bqyD*8qyjc!s0DpOmC7;@f|2^7PS)iu zcxZJiDm|%b%3=ItXP`QenJ+O?n*-|5CCBuTv;c?yX}4K(mPNCIEwO6f-i4s=n!PTl z5UuTiEU3HGOP;INlD}W}NH$tz`g~Xq>4Cd_;!yTZFQrd;MKcZxmS?5Z_a zsFADQQqk|KsFzp7n0{qdze7Bx+p1bzdCv)14VVdDAz`yd6VnK=)w2N>+s8N>|x$=^aH`%R*7hN3mNyco5$ zbY5)tKWOl5{>;<%0Ld>T1Detp9(b?w?w1kug(Uz5I7s=Us zNZc$xRC0tIrU&T<29ZtXBDRL%8PP%|9y;~sJxE2-sPTEsE1#uE@w|LVrDz(5@j+5w zR1e#V#4;eLCq$P(_Q}JfOz;JQ1@N4!mB4*Hz(H11v4(x~x}MkYxA5L`{{D)>Wmk1C zl?doC>`f`Kgf($NH@q!;07)dvKOv5r;pfeHqYduV@|I0HQ3zzUK9yByawTWG?LHMY zm%XBtJD)ql`1LY8}uMSt1DTI21lAtuC{@H-^Q8I3!amqt+ej#YCt_$ zbbO}E|B^5CI=#GY$_6g<@f+N|7h(PcVgle zhIgozn@ax;?LY{@UpF_DZ7R19j2rLac9;4v#B{En_)aa1Gt4SToS9^@7Fxt=VTx_l zvLnMjouF}3VQzfJUg7^_hSdC=g>|0qj{@rgZL=&2fEjg&X6}gPg^12wQ6@|}Ry@~9 z5`0$yQ;u%5+7oYRFIfYC8df1-)SA1ndA?NoMt&cuIu$kLFtgt~zL=t2Z7X({tz+6~ zkRCgfX|J``_4K!AzHt`58Y|vY?XBrk!Q_XdeY2~5jXB@2_Yqg9{E5T5zwT?6#ZyTw2 ziHen(2^$xO-}UI>a2n?F<5Kav^}>~r<(YNqUjie#UlS8}u5qT;GQBc8oH5=-ePR&jD) zq|+@cwyms-s;7^YfxMZ;I0qV<^H7=(BNvdo<*yKYW}Rz&EUVw-CaR60*49%SaphlW zxU$t5lK8K9Y)i`a`Gnr+&mjHnAs-A*smu)fn04EaQuADpZwudkQg^a;7LQi2)JLvr!l!Jr!}x(KGR6 zk|(8_7A)9)espRwGh4_NXS4Ytg}Bo|I--HY;vfS_d;>zZL>a#UGI&jZA6BrD{Y39J zY_}#Fn*Cp$iDI0~)Jw=jdON*zrq!7!)F!hHK&NAFoV!u{9Lyj0m&Nyuyg94>vvs3G z)@*aXM5FE(m2b5RzVb8|Kp43a{?|hxhZhzEB+TDW$TfNCTl;(82}hg?(Ko(^i|+zk z4%!}edeyN?Zq22=_#4s=#^2Skfu$errQXgVMczJRJDq4L{*9PbwXVb_Ts!%ippADM z*-UMb+ZPIhQLe~qlbLijpXH;uNt|S72Qssn996FY&Px|o8B>M8(XZ-|GjqVz|0wIv zcye$8>xZ-FM)nY8DWhkn`R=E%IaA6IXY2r@q*odZ&TYd8tmCVQ;r~e}b>eZZ$6Hu> zUuD>hyvo)R z@;cW6XyByP2OrK6mNtK!GEkGvg~W<~n2SVSc?UZfC(mu;2A#B!p#V1e8mjTfk?xT@}O_t zc7nEcNEq_BxBLA;sN~NtldDSM#|qtDoewK_T^>0-;x(DxqTl&npPo zGsxd9AbnlctxHAUa#}_SQT$Z{6CqQas0RX^0@=L{3N( zd^i_Tn;z~c({HB-cAkXSPIk-b&c^c}sX80Zi#-4$D5W@H z4|cPd!)Vb2ZTXqsIp<73(P*YVVozo39jAPxpwM*B@=D5~mH%qqTHDmrI6?|Muv)Q( zT;&(B>=MgbFnWAe;=%6uw}-uZ#q#o|;DA}uDZA-kKHuR+g$0}?Rx3wciE7_)+c_Z1 z^;W(zBc(k(;%x1>?nq}_+lh`rp?9-?_UZhhbvJcPWYbntZp(kfTFJ8foEk8% zJjKRTmWkBeY-)YanFWobHRqP-)Vl)X95*Mok{e{{s~ti0!=lhOw+nkXuHbnIDEWJl zgg!~|;EF?F|~Ud1XcPhGmZ_E4#a^_-l+Su$ZkB**c`hEcj3XVo1C9VsnMF{-{$Oaz|R685$kF z;x@7CZPu>n$RH{xD4aibL5k29LjraMM7**mIwU4AC@9c$Shi}pgo4`Y=6?s?8yHGK zzcUX@Ws#%KdlVTBza8xgkVUS~k6s}Q3=B{Q1OahTfrEiTIQoOV z`=3>>yZ{sZ1A%`j(NB1D8DvZL%f6UiD;RC-pBK>qV-y-{QU;P8qik5jHrW^jrBh_! zGjtRcWf9akUa8h){z1QjSJTz(^Xxc%kD#>Z%}U4>nxmG4xl|f;$H2vY zBfeWk7SotrL{`+#Vk?Fk@2@*wcYznEDGGYWZ$E`*v4}n2$qX+d5#Z%ss~FtUd#W}J z(^2>6HfEQy_uWX|2zidYtbiy({(RVmnF%FZ;FBW(@oe+wg1a^V^QH&<(@tuP;yCV< zBp(v{HUeXK4s%e*_)8oe?S96HXe1)C*nJ5>RZfQc95XX$e_9u@~zh+CHz3wSde7zZ{N|EuABWP#q)bReLAQ2`=o& zwQrpf82+YL~3idhN9O^kKVlyRi*+@ZZ~@9&K<89 ze+U*pyXkBh<9Y9%-6MQRb(L4_1r|B4%VoEBVW$&!4G#l9J{CuDb^(E*Z{G{(Y)=o2 z*(V5aR0%*9+lYDW#5N3xvG>|J%(B9zlpMyG72TviMF>SrighUb->@l0Fy`wDaHNi_ zPBKwhociG3GiP`0_Ho^3!HGEx$5n715xetcZ`hRU8+*GrO#7hQe-H*_MIm$+Gi zHCh?0(Tp%Gd&5k_^c(=Gdie=tw>zJ$2?pfZXz%*;_3O*Pf7i;7eD z;OmUe_aQ>XVeDO0$#uBm+?W4}8ET+#JLBhwwj6$39Ya+jBCX%-`_~NanH_y4)H7Ay z8tDxD>A(M_CQ`jE;h&q^3l%**;;GXCxzrT3jJj8zH))zfsp*ERk%ie=>-$XMtGkNK zuU%dY!sWi?wJiq@w5DC)Ssqb`ij-D zU%fQ_(;!PHHK)}#rzO!-{&9hIy|=w{(S2$m$QV%&fZh$e^{1Z{KmQC=S1D+_6caxf_Oxx@@E3#aA*K0|T5V;|?qkZ2ZJTvjqh!E8=2H zONVTOtHRJeRPigiq@5-l4RM4frmYPigI4~6&RQ~m^l&L%@W~XAO|7(|v zA9NO_f|r~1z-!Wc7u5kl44%6n!Ywg6LB|t~NMSCx|IGkD@CQkcQsei=(u{Of?Wt8k zeL>5l_pdEAo;Mf%5P$(ey+LcvTg>OrgJ{vp5x-mP7yI4AmObkNsUvmSTcZ@)XNY4j z!H}e~QJGuH=L2Ih_clQO{c!5;_OG6PTAaEsczz&K! zDvS2ZVG8Vh-ZN*0hx?jOn%xd?b<6(!Eo%)eErwUd-+F7jWY@`)yS|JOGp91e7`X@( z1p$42EpQQWTw8u|*yMe5vD>a27Fw>$B0o0{dQ!R`##}TwXvQ2iqlX`l4og297XA3! zMGWRKpiP!qjCm(<*l#BccZ*ESv(H24tW z{kkKN#Y_0Q*arU5aH2DKHw|v2TYHAKJ4BUPp-|laie@rxlCAh}PHT-ygF|S>Zl`w0 z|6;=ato$2_`sQXsAm9+=VG#EuZ{957!>LJ%V~*V2wsze?ce>!^?tOK2eMCkmBIB>! zxS?cOQ4bQ&Z$IB>GKZJB*<{QeUp%){{Ks4j7!eq27qDPo#2kj3aMV4qchrGwb0ENp zq9}4s5w02#bwU4^?<1QhT|bsTJ|e1OvQ)_zUwx{+Dpc|%dFq!n=tzoQU$ETdO-US1 zNGY!B4_RK@yBL;OR2}s3p0h}m7X1|U^Vd-FR2PtUV>f4#EBL8N8NyXwHY!63{f#=^ z)t0L|PRk|q74{`?+I}91C?MyW;DQ79+`*mqX37PY+PS%PwRa4wTbN}kx_pq-5TJ+< z;=?!CgJk@-m;N#j@<6a#qIL>YTkW=!&34-k^beCa3Rk#bvtEg0g96IWK+C2wI>YBY zu$H*VzQu0mEyQe=h4zv1RUAEzD}eoprTybC%j~;L(9u+vv<~bQV9lLpA;($Lzt|c*q<9Ff4g1h~b!i zEAjvODGE2{-a%i%eEPVwPd5I=(#PKtabSPoX8ry!#3A*FBHHpBMbR6yW~jH@j;Kj0 zJDsO>a7`JXo_#mfubHB3y(F{scbhYap}-IVldB*^l)Eh+FMd?~Cj=}A4&)FBCSZ2$ zuCHHXL6*#s`jO0V`F=ZTA{SFt6mJ&SGk`ET}>{?Sa-Is{&}EW$fY^*63~_zK3;U@lBw`_nSDyE zs}uL_tvjza%WLH7Q$sTa=wO{yDOypv{Ml#MM{1OsNH}1>v5N&m5u6$8Q1IL#(F!`) zkZpvtMi+{JQ>!APBc5QbDs@Ul9D)e!DLgFX)?f76J#;?@^v0k^ zjEtV~u3F`VmMxwu9(>RhS}|>-yQeXXR|cg8{6$N4JKz1~zGY)IEj5I|%(LSs;Re>4 zT!^Z)*G*%)Dk>|w9L39e;WhjAYjNu^14qCbD^zE#$oO+LXn&0RLID95Q=#fL1A^+; zs>Js;ZdZMAr;*#HZ*SJLW3)bmX|8EnZQ!`Ztx7IkO}UDlk1OZKK+m)g(WgoYLdJS; zr_FiG%3uAGLCJ?``{SG&vQwV+0D&gRgw-XPmAECBC4yujbeWgX=!S>E3~st-1PmnO zZBxtktP^Mn$z3K7<@*9BYC?73Eyw5RbFHRE9nuAtwYQfAFMVafa^~x?{vL?b#wKz@ zi>aS}`rXRGR&M2g*N8^x74P%{j&QY&-KJ3atDlnr{;4O6{#&M)4TjSugQr|RcaSIp z9On2L5s5qtiBiFcGc&Nc9P%|6u7SGs(NXs9C<}<7RGJ`B6q(!&@xsv^zaf_zryLWO z?FcW}O9A4<1e%DM3Er`Dkb{3#s(Erisrh)CL%ebQ^F|hoiI9a3hez$e$R_8=`jL_K zKD|lQ=x2b>jiNvi=2Q5j6D>ggezv|c=+AB6?S{JzW&pmM~{YdsoP8)0}o6lOdUNkuAK7wCtd2u z(ec+0mhYV(9r^EnM@D^KSWtUDYUPIV_D^L;kNW+beextIAzzY?s^^stE5QUHc{qKv zL|&_-;FQT|9(?yvgP-MU|GZpDl<~`U1(~xG?L`3!pU$TMUNs|rv?ESNmp*Ge?`UtCIz1cnm+$RHX5mqJJ`TayimjWv=!4{C)^cUPhB*Liho&0T(W zfK?B$t1b1g!oPH2e{0d|u5h+5dwq6gclYt`?#i63b=HTut!zswnlnx2jheB20?W>m zC&Dz7cBEWeRDVD6UB_g~3rp2h%2L0`sbXF|FPWFkN{W-WbpGEIk>->XtDcQc^LJE~CQbg3&E$mOh@8X%<=3(#AT8Jdenv=YXU_eI72xcZnt(2L z5n;r>F{Ii_TEV(+De;vS6^Lqkl$e%3X0-{ZFVg{iMq0~Tg zNu+$F;YD#6K#5lpp(+c?p$mfrj9r`Og(>$YmWG7333q+65} z2@dRWfUda#FOk+2xU zKzxn^H6j@QhR=#zxakqmG6IRQqnyVfdc@xg>t2+Pk|||T7G{oN1j|3itJ)R|G#_hz zhmWKMR09%b4y4r0f0aM`7@J=pj*hC=G5Px*dkj*QD$2Z=NKI+RsfdclmAWf^y${q) zDJKU9ry?V!h6X2rRq9UzrjY%Zh~F`iA61KXyOaENk1I8`#N|REasvw+Ug? zNAbO51sIj?)7R9PYxGhUvV|68B1}S!SJp^DcU~fsDN_thHAw5yyv58eCIr`a*MyxRQy+~4P(?9iCF?6jJf{xsaXN#vH$(sdqV z+NwtBHkG1XHrp6`N^!oXrX98OuH9lmU4qO)wFx{e6vXtDb;0hy{|t#B2&@}n1Zc6q z37CNT;LAcoUYhhuNI+>`;1w+3rhqhPSGu-LRuM1#XQ5%+$`?km^3$GK5gPsTPm5gv zD+3P1uJ|c7PyhEDS^&pk&M&frC5#)n0W^m={|w8rEW;tLUwcji_@P%5-gKJgWf=Pf z=c>1535f8BlT_8vZ)M>s@s>KcYnJ}FdC7`Dn`;{5imR(%R>!z~9(h&d-07bu06gXv z*1R+D>50_|4Qbmf*Hf!q$yF{*`*pc?Y8oNWXVY}o_6Qy<2w(3LbRV$by;73pUAVfN zM+~yMY|uljf)y6j(&)z1J~4b!&5P6S$^oJWdxYs_X4^zL!?>*q#4gw-wdgDH_ciTYJ2vn&d&8Cow^;TSPPkW(zoJ4XH8eUU1w zq*7l|+|~KZPvf%^T5^$^)cd2pP|X@Hspj!~9?Y#c^aRrRbhPZ+A+NOhcBLgJtEjme z+Hy(fgr~|tGLJzjxbj16EmUCQnLa+`_t&? z(Uh3^d0SFYRg;o}hWE4T6JJ2Ok|@>TdFADKs%>|-=DZq&zYr3T&%E|@bo^x{Wk zW9`Q$#cGzfzk2(NtOs?Ux2`(a}4aYQ(hIiIXCh9?LiQMND=dF!Lu=n zUQsipnZyejTLGHGN)3yMMt(9EuQWdhZ92!tJ8}KafjVqx<_uWp(_tl1GU8&>X%6f_ z0y9T)0q=c=kv;JX<*lAk!{+v{Qi&rQ0Z;=5^9&2i2hL0%Jc5V!kI-j2PSGNL%CQXU z5O_{v#RKTtPauTyol63o17q_pm!a{Ay;RlxyeIgd>$5ZpyXe+p@ZJ0{S5S0#8F*!i!3x z9UEI4xa?lT7TN@h|v^nOk z_!Wzeoc$(p2z;{$yzN_%=psVv_D36HP@ZqBRdCr|XB)PLlsPWjOZS2E1d~Bc2~Q9~ zY>{`f2rK!gxz@D+C~v|ivfwavAg+^ zqsXaObpC5@>3q6RDyd3YrKYm)re-qjsEj(AmR&CGljci%r7uf~n9oUp5R3w2Ase@s zNZ^Lqjueu2N!TwgN`eksN^-_}lx#{~`HRA*m|%{#-9RMQWa_9e<=$}rdQ$}iJw)(i zqHMuh#@UK%Sx+ z*@EmB--BkW#`vDs+rz^)22(Sl&5s)4onBkGl7S1Ta3i8xs(VOnzL5)8goi04B;m}0 zK>-Wsc8aDmES3z(jcbQcyo_As<`694AN*;^Ai_JMz@FQ}Y^YU}Y9_4I7-;sdEo8uP zT_Fo)!kL;i0Z}5~vH22rJr*pswOy*K4+xUX{@g+mB%M{NA|f@B5&u0i`$T``QjpX? z{r|93#8%Y{t|`BKik8QE^<+iOYh3!~_v66K0z-M!%n83_d1N^=k)iE5XW)W+U{~vC z8ES)*A#Vyy_U|mLfSR;law@sjRSI66yAu+kZIy!LpM^PTr5a2h&oG>RpDmrmfE2mLG|#O`%vwv0?*CA>VB$jBRSh@_~G zXv)6|h%%K*EeMN#Hbx1%t}k47v~1mx^R@J=_D|Ly`LwK3b=P+3^vbxVXELT~2YS!9 zP0M|q|F5SajUI+QB>OLiU`%(@RQ-fW^WN%_k5QoT#fn4y3teyigx`;?$cmYJYrnWa zM^heTL6AzRG0o(AH3#^}!XZWyY`ej@>+2B0TJ_e2F_DXm{s?PLAqiC&C?qnSrl~0) zCrR@Jv+Va-LhvH;T8rdjJz=Lq28vEyQy0dC5sIIe*~qX{s^uJo^wv;7`^lB|L^ma zm5q75Z@k{y`}!MR?^szGkrAM=K?mzxKTlgRF$%%#H(E=%)xQyocKAutSiTeAo!Hct ztm@9}JyqTNXkt%x=P#;$2s`tDSVW?B@js4S+{YiNi25CXI28mc1oK>&+xQEMvz5jv z5AtZIkPae2{?D&Sf5(yQ068nJk4*#s3AJ9uvaecXb@zinIemdEelzzht+71%Oj*WQ zZ{jSca*vDW=a__gj$g%8i&$iekqDDNT4)ENE z(dP~b(O2K6b*Ba!c_(s$(IOJ_XE;k#QI|ffucVYudrjTaLA`5}M#`rWv-7gkM#g{< z$GBgJTT60Sx2FCvSknDoyfqF)OJ96KPJ6{T_G02U|)b`xA8m#Rsn~exLdM;@oX@IjGC61K7=jxutXV1mf65p|>{l9FgV!UaWt3ZzuQ zvi)8$?6h>>C^A11sZT_PfS!+n-Dt5aB}5Pqhr8bp8RDTZwYJ?;YVG0iqZAh>CTm{| zkE;G+(jKuQK>}jkKnXn)6cbMfg2vRcqZDTKw(jDX70w!aLl^L#rN(5~aH?*>;=!^h zJPTzZ#LHn~#Lh&dY1+ujCMgCpafF(b(E#tsC1V=U^1n5QU>E1vMf;2cKDSElJ+b(r z4EI`{N{bA~3QRiu48HGx0DBcD9W`cacVaRWhSGDc1_sBf7atgO`8~YY&c_wkbD9G~ zTl`7Lb+@K{U3@e1>s{7YHsVc(dQR75#arxOij1$@wfTa#;15Sfe>akWBiwzx8+)75 zbtX&PXUde@x9=NH3Qk3Hb0{@9Y52bK3z?$)OxoS3RyTG_!zv+a0SQkCUTZv)<*fVO z&)pD%j`|Z18f;hWPe1WlhWo6)1Sf4Ci<}Om?MQlAoEjD_i6}$is6*oKP+LA{#OVC4gWg90XsI zBYJ%x?6+*ewNqL)#w<87RWbg8u`5+#2Hs)4=-iHC%^1M~V+`>T3TBBDrVO%@Ce>u} zrLF*=@|`r#nmH{$N)ev35!GNv2XFD$=np>>MKd)KcE)k>s932M2$!hx+*+fW+Qs6BMJ-%@Tx z$ENGlC=PTDgBWc)Xbhh<3qNDEm8D^n4BHmDHkML@RUBv@GDfAGE=j3WZzODw!<`)R z=bW|9svgtO;eI<+Te~i4FX^vW^AgL2%HsSdo3;jNwUXOvjQ_R0-M%?* zWf#V33+V`ujo*N5&kPLIBYt5*n5V+>eZ!sqxz~tu9Hpg{n2aLE|f zpeCFDCz2sN!^ePS&{ixH#X))x-xDz8;V^dEcQT}LTVr7K8RCR-lD+&h7_G}%h|BPn z-#fE|)#X{Aw|TSD6Gw`M6URp^eJ)9hMm3yMr9HliHlfW|!GL(d_N1o3U{$H~2GA>- z1O?U}*_O)2Rfgu~16;FVjim{C=|q`Q#zsp_K5w{*LBvXP_@_%bnsLUy58TyW+-wDW zl;Q4VE3EvFr9$$nVz^}s+(KvgkRzgsq9OwG+BNUd%DljtwO(BpyQ!ry_Pd7IR$mN{ z!FREZFG=|sYbY~8)|i;t7)|?o$}`gmHu3bvXiXzkdPEF1YF1Cb;+FD368YWk?;L&& zT$P^{9X#CA*x)hVbk?;y?OJUu(r*Y`TR%@X(_|Q$SsIM>dkD6h6|~|St!4x@QmfU9 zIwn#Ur5E&3GHanCQWL2c)QFDMymAhl3&g~X-d0NIoFkN2jG33yFEgfUyzp#s!u(0T zIiU(IzInV$nA>mU)X0{GyyxzoOEJuf2b{BpidOqo+A10pudnMb8LvDx4tnLcT>Bw7 z>RbGmlFH4Wj=wZ@Z0_i|XP2*I5r4n>q1rp%3!9kD@kMy!yU_Ld;B|P@ge`P2?fcq%YtOG zJZV?JeJAc+vHP!s=9=&oZ@es96Ko07Ca0&w2Ddc2GaGha)WxPh`7)LAWD=rd{_yIW zp0r>{wtWwSE>^`ZTNbF1t_*ApxKB7k@BV8~+v@!>tMi%Bo2jR--BtSkS4tA%eizHr z{%|_!6k4&X+x)c#%b)v@LXFwVlz8k> zFSTC%_0tcWR2!qs8Fm911@rTHS_9X7FWI+GB&yZ*J!{n!`T5-1RpouYsk3R@oH;#+TA~h2j6#408&*ihkIr;L~0jSSvSNt6A5WA6G0J zf(8ZP90poNVv%4CY=p%eCnr282cxVNaFNWitQ+AF!qb9Zl%|Y3k#kX7%XtJONI=qr zxcSf=;SP|}rGAcZF4se|7A0~k$8mES9wbUF!L1(beUEWq;+TPxa-4~=;1S1Iz?QyAC zB(E}wRyR-?H!=E9oN#NWxk%ZkfxJoxHZxRQH_?OW!&-2N3zblwc!b52q?woTY!912 z8gs?)5+3h1TM1s$1^fE@*wq$vFJq58tfp%NqAfrU zkbkAnO>N#>T+9_c@iU@0EzXD#MATHAVoss+%y}$t59gjcJv}pX%&IM3<-RsFM><}2 z4$mPBk=*62`tnT|W*zr%XilLmV1&o&7TD$To;hQ&c(owhn4Hc!w+EdpT23_&7HX_* z*4u#GV#IJyMP2g_-iOG@+eaP--D9|9m^C;JiQ{eFw$IxZ+Dx0iIE<{O;)@E|?CgF; z%#AU>4jUI>+rJH>!TF9Q8SRRZWq!j4nn~Vn9-y{Ck6k?NWxXI97oBzIH>W&HQ~B=1 zrgRhYv_e$O8vTBn^d@i`soIx5SK(P6*?2tjP0TynR57%m{G+oI^KAT5JRlNY`>rNf zp7Bt3<@4RfjU$Y}Fd^Ihd}ViKEFiC@rh`NtVMb?V9cD3$4`)4G+54>_eYxA-Fvre^{)m?{5IPk~0^1-;DDMp-JD`YJd3Y7oL0W+Ou-s zp_|}&i-g1TbBl4FgH~Wf6pR5vI|Z8U1ozHTa20D>gVarUowlILH44s>D^_U6DN;qi zgtwWRUXOzL?yc6SD$!+C2XAQ=U08tiiGXPaGsxPzGb0<3VJ20UDx_*s-QZ$=;vdoJ zmWLV-X1*m4iIU4QXJ{z0@Q8@Ghdrd4VpCBN?7dz+4IktNC|EzPp9A^@?`SPBIr z>=jgv^^V9$SXRN|XzFa_uRfAHGbWjCl z)pC6qI=^0#;`5~_{N>TtgB08GTZ*9T(FOWBaaTco5QHd81${tCG4@sa4Z}#CRG)#t zMq;;)HQXv#R}}eT=i^S<)Tce9ku@Cj!|0FS6BCx?irj-n{_x`-sPH=neh~4vv7`fzc@uz za7K{=cq@!R1OVMMA-eQ}0k;nCPc4d0CbHNv9}&r-*M8H^EHD^XeN)T2u+h~exMA>2 z^aRopms;OIr$@x~>zELY9I+G`Qq<_bzDFPRk^;Zf`Q(#}(PKVKs5i9MH|Bp%+1ff* zIp(mld{)1K_1{e6IlaEU`Pj^)dBMoqt|Ajg2EOsR$1&F$Y@o*i*2e>KjB|_9nBRSs zOXW)OLTy{TjBIAzZ@lie+Zo~EWud!9GSlC?3#;!g1G{1gr|$QiFe=*zPRq*OU!<9& zWMd-E4G=aC-oAbHsmlGn^6K_n(mCKEu|xmpqa(v)xX-siAAPU;8Vxz58-HwTR0giu zfOS`Owo)ahysj<5Rf0qyMwZsG|FIA}0*&QXPHvTpn8U(1_y29$I3+uZL>i1cyk<31 zl+2xsyDx3*V=MQw$t4%#nB?M%@sfFo$g|=v7AG@t7fU4cxndDjM1M-+V0Q<5;=Zl& zlyf_3P|uF+WoMSr|0;dUh^rPq`S3IrKCJ!-0B$izLAsj8nGD;caT}K8lM0`&uCB7u zM-N36u$X9{-k;{_RgXNfiiQuv4sXo!1<%LyK6e6dze&xcjM`eh&MZNIBgHEpuMd~m zR{VVZ$Futfz+|QniF&cH-|9dP&8O6yevbN7gEdunLttd>*v6j1^XBIJ_4H!HUH&7k z8T<6pg$p)1{hMlC8FW`w7BVSI{3;)=p=iK0kENH!8;VWw>5s+2Swlk8{EhqS{OPlo>~5R;(YknKK{gg4KpdQbhpCDdqeC`g)3Tf)l;i6OUe`p& zOycQ=>0DZ7!-SXXD!>Js$F{LO(Z328q7vU#2Kou`RKrwm7}fLt*bCb7&)hkRD=|k#*R@R2r zVE`EafLkIxyzU93C|vT-2G%HOc*HB(m^b_=fQ-j#1qmz>17{2jVxa~D&ar6F8X0h# z9BFvoTAwzqa|`+9Uw-NJ%kZ!lP7LBq!xD%(?S=Mt;a%4)(}1@l$V{_(@r%I)wot3Fd8BV61&t-t+Y0-VY8&Ea8v)W|SI>z#PVgW&|$ z)&cUbO`e{O`Xqodzbhgwx(CF*V=p98A27? z!dy_xz9{@6Np>DQSYF<@uw_fE@z+paem?bZ-^*YEnn3>Uu{V?3u?NFwl2#5>El(^% zd5#UF2lgftvdfQI)bb~f z+S1<6^Cr6k$YTelhc+oYqfFt7dObA_9o04 zO-1h1-J3}T#3#(x6xY{@)ICGG-G`mdc_u8a?oDoR+&a!e^gc5~bjhg7Vn3H|q&M9a zSlWDZv2|VuGNXQEEA_-yWF@@*w&A|sX*OOX3rR|8k8mvT$=Z7TOPyn5U8rv7&N}&` zK0#RB9i^E<9bR&QjiRC$=5vATHu7MP+|sk(jtnc(6@bCXmYbaRfhzb*8JZ3`~3rQ|ZFhb>bWoXqCZe7f&j`y+qpNYRKLIm^Bc*{mCV zr8MChSNIl!$Ac$0!uR2er)*QNtWT}BJCsD}6a-7cb5-_z7mhyAV|Q|0L3dR*haiuU zDTyhO9gYOlrrl&|`Ck#Ajlq>ehhQ@EJPfVb>CqjGoE4J(Z(3_lj>v}QeqX!4-uP&& zt}^kS)PdB1#vADNn(RBD(OegcCo=!QX+K5U4+{-(2HDGv#p!?hdsi{=qdv2Fo02H^ z$1KDI#Q1jx9#!TT4%V69kZ+&=tMjx$-y@yT+ut7T`YCFhJ7Y4~@t+|BZ|ua*`jK=jrQQ>24%on~_0koZU`rW>1mr3EBQYW334w=o2m2uioq5-;SS%RP+q{q^Z zqV?CfamNeW8G+HCc_BG4`2|y8!uZo_TM3DI_lDG`!Nt$dFHFxKoE4{Pr~FGxogFb9 z9b(=3FX+AiOpzD3MSK|BUMAnHK>kGolg2FhXBC5s{+5B4mzzA|_1FC)GkwdPrZ|m9 zoX%b!Irjc==7Nk556hPYWbKKTjmg4mcHGH;*HPJ5^^8{DKZm9!sXu)FkHIaJ1=yxW zb_Kt5inm>w0vG&(oj6nOW(ZTwix?)|D-ja;OJ!)BnP50Hu^U2*uF*WB>bZ34)Fme= zcL8%=Ik`kmny02_9;~ZdPEDEWsklUS2C*=nb(xWXIlT z?bZ;xy?@jC?8*(Tb@Xh`$<1#JN}QV#bF3fuL>jQ7GkO8~8s zC{w60&8*iun>u^NjcCTGl>J6FjBu@;Br8g~oPPX2i!NPkGU@9x8BBfV*QqHg+-fjb z!>Mssv713mEREh1s~7aTCp-SQIz_t6us(Lr$eMcKR7Jtz6%E33`zF>mYmzV|7eppk z9E`;b)|{wXQuR#OA!I^_!Y(28`AsGNjsy99Sc>e|N-{H@TbvQxrV017UsRFip^*6R zOv+XpSv0&Uv#wlO^HDSjGZ_8R>a66i*8yMnNdOYGp7kEBut>*x&5rAu$>$IF{u>{t z?b3k8fQGDIje?R*QHz2i;Jp9tG~Z!pRq3R`htxngtiex6PqwA`i%qpi;6wDA<^AH zNaxdqBxS7)sj2TDmhYav(6CXW+^{@j^&JS2o8cS$bjr~7r|P-x*G?4 z)t|9y>KLX(?YKQ%RpcpB`JHjj^5yVR*fyA*jyarurPbz2hGF>ce5?Ghq$l}L>(VW1 zB4eShD;bVaUa$U4Y7}lMywXC{5wStB5j(y}pGu#^jiA=3b_I?8+14I_3WiZ#=JnO1 z9{;3VUqt>V5pKG%WL|=>0Ho*W%zZxm8+2E$WUQCnTUVmHP<7I;D`}z=i$9(CKx?%9_NLT5?=Y5Rg^M(G^ z>~bZX4CHcMRlji;yTnnTS`w&3bnA^^M;~mV^}Gz^=?wDJeRUego}S5w;s;Tl)fuJk;5B&17iHYrvAtFzw|sO%PfwnY(|ZX&69Vs7K5#ITwTZypI7=^wG-?hL!}%gHyhKWqQ& zvv@t<(Y4_Fy%tMctV#6ks8SGBSAGKnj_qFfeO7Y!?&gHi=*Ljlm@XswXyWH500+lE z+S=d8^X26v>ddZIY`JIuN-Qa81;@V=kCjxE!Y#FCM}F(`KdDN7(m(9o!b~bPk&dVo zWlEGIl9Npp*f-sVv4UJ(Czjk2}p2pjX^ws&1QK9*{s-QbQi@i^``0U zongk22RX>8wFkjNZTRp+#G`BmU9##Rk?b7%VhZ=IVEs%uDxqDlra^9wmSK#S15b!& zg~wxMLj5Tkf&(CGxR^bQiC#p3MA7@;1AX4H|8h^Yczz{s?P6HMvdmL1`R2~@;JztK zzQuL>e^>=F4iKTkQp9dVM)>CM5@`=@&9+KI-hCqphY5=~;A27>dO=-!#-qz5X+r^_w>MH*9EV zj`ZJ^)_(;k49gN$q;T6Y-;1qs)i3;e41^a6T^e-sZ_;LaMad$dTX6Io?YfK-&4r+3 z@!EuX;uuSGuq>FYGq0<&O9adx04^h4g5i`Oc~Rg5m3c?d-YGa??`pRoEd8P=fV6VX zHM3UsBO@q<-^1Q?gz?(lJv7#};aRsjqZEv{P0TONB>6ek=n=LIz-ac~FOZ9u-X(b;H2t*BmM$YHhBDQ>t zKHlPm){Cy&S^wgT_1u!dp6UEYjC|ooHRQG8uI{cvjm|l@K^-T}mBy(XCSM$o8z49} zB!Q#jTvz#{sZ{i*CG9Y_s_WKkmPb@}nI)1&#a)FTt%0cVZb0hYsQay`oJ-0pD_>c( zabwX+z4yF~{H80WwQ$m&pZ~F8okBgMj&}}a4msnYO0jOkKYpg#*Tor3;x1)>tGlt( z7rWBUGgb}^a#?<7Gg9?VZ9_wXN_SJ2=*~LT?>B9JF6x?rd!+Zj!)tw8d|UbsV2aJi(m9@ z2735}Q#%f1edZ1FZfh<2-NBn~8IT*39gwY1NJ*dZyXNoyr8Y5=Z&Izhd!s&+ol|he zZY>A=^1gK?DrNcH8TpA$iaa-oh@@yIzFlltKT&ihJkZ1lOtDW*BY9+1H0ik14D?cv5~2V09Gfn=+c`pPOHFyWLVZBT4r1x2DwEZ#yrJ^ z{sRDpS*H@Pi>VCGbtz3&B|ZaoFzw#%;i73>}8!_{yV(CDNmlObGv5H4t z@#Mp_Sd$UFGjeB=CT_wVv+-$1> z@wZlvYh&oGo4^TI-xvv}yuVX@UiNRR6tO=4316&Y{Mg&t&V_4-BpF?Vks2T+I0;!u zsI{9VVzRch_IDRCEMWvBFxM+z9PG2wZsZ1Xo1*$MHfKD;)UopXGTIp9DC076^GQ~| zq!c=j@Or;f{@*2F@JPzzhyKHX=f|zOyY5GVw^@#f#Hkn>siNqziLCe6R^}M`rBZRu znt4BKB1@>r$=3xCZ$cumwUtdtnCwj9J>L<~p@}i2|r{-hEHX#xV3C zdP&UuhtvPXtgjDGazKEjIdW&EXKj#qqqFxmPnnBRBAwr|7Enc~mUu7cOs2tzXUf;Kn4}EWx2zfOwklUnPi>X0y4H={T0nJr zVz2K8Lihch{eL`Drt0>M!G;hxpnPW)2VwhsrjgsX&&XxYZx={E;?N!!AJ(3TaS2J1 zjmnmoa{2 z=<}02=uWx*&uI+%$=x$U<5o zY6pz0lX^6r7v+gHl$~M?1bzPlw6LLaW(FYz8dfsrX~D=dBJ;=yG~@a$1C2dIqL;WL zZ+ZGJ-X^9t7riw;{?B^!bfP)ppOvyGCQ3Ha53LfUsd>gF`7_V3JZCOIW;6fFGaTu7 zF?4%#mW(}?3$&b{lANx|Z-EeFEo;X6ZZ*c_F4c>=MmKW13&W&zmzlgbc-|;fm_0D- z^|kqmPHRX~D`z8tBuFp~$P}6zoU1ZIfrx&lEJr*uFZ`*3iuM%#N)gb*9+9R(*4FlNDV1kAi;@ z?(_lrfx1QHLExj}U7Vfk(8qR{Mo-Y@I+ZeaDOV|NZ_mx4B7$Fr40wCzIMdC)53=mG z*C(&L?=QC@4D@<}iQa5J_0f2Ru7(-sc|A@p82ST%sOTR*WR$ZkGl%9F@XqZd?t50Y zb=IuqADx=&Rf4CdDp-t~nC9_$;743T#pr6#F>0BvXnKORfFhZPxvRxay5RZN7yk5JD5! z7++@w1qfZcvh0&jdU>8@@4p|$s35@7*GeNL2(YIt#!fyRWZ9txfK#eKtqt#Y510Y= za0$1;Czf?_%xw!h0wX;~%jFEsV7fgGh~x(8e4~c(FaTtuZBPap%|OZL83&KnB5TV^ zxhL0fWs|rRnL)9iu=@m0kgB~Yq|(npm9r9#ki|DS7aW&vOhAPUxgGe8A+=7WAdnU} z_(y8nvJ!Ay$&mp~hDE&$_w+dv)_bFuX@I@#&VSlvN}>!px$zmdCOCFt zLfpGoG?jbLtgMT-_CvN==VyiT4DXKYx`XA|K8bg?eE9bZEhyM6{wa&hL@)me>Lz*e+j$~5+xz@QNgz_VYJ&UGEn0fP(u{kN=EDXA|= z54@WpXSDWfZe|-;{hEe`HAVIHMfnN>LJut_8gnVJt2jL+ic`~-buGRYkmzy<#yFF` z{4YEvID(Z_YQm4PC^q+?K8l*uOj0N{>PImG{Y%SRup}U%=@$G9KD38DBL-vo-$iY- zlB`b^SsQJOByn7Y42|ihU0*0X8)LOFs8V;R$?BL0TG=q?7pK5QkBM^1*w5I3ek0>D ziUKDv<>j+!wlpaAtKxTjo7bQ4(y=1f&ZM{B)0J#^YfIS#o`5|~THk$pzq*0mnG|o! zZTj|9e?s%*u}8;tCB1$0%cTwm+~ANq)aP%b5sQa!H_$~4jn#WcJCqaIa5IBG9OrR~ z(}rFc`O(%NBnv;%!{PXG@6MfLUiahJgJm%09iZ0a^777q-*CI6x%ogdIY2IHwi(HD zFevNa_Ro}=MZrax(YcZ7@r|X)nWs>&ws2p1ipG?f9S?}wSk{W z4h1RC{5~r4QB6^Jc-ZQ*K^pP5Ed@E1#f?#c<(oKy=!pl!pmHNAl@Nn&s(b;>%!26D^t+QEK zvt#j)DAnkzYpY1?s#Vt#^SHdNKN8)U^}pmbc<1K*vfjY1r3E_UG5xthgsxs;K?HvH z2LHCD6>AGC*H)C)xmfC`%!X_Nlu?)kC&JhPl*CGFCtdu6%?&M|t6L$sad>7;raUNm zXLxeNBavhM{m>;7pbn^x`dTVAN1&GN+L`Ap@Vn{gr|a*K^HG8<>IP3`=)Ag&pQ?1} zJ830R(jod!;~w7_5YR>5C|rqF$JO}EJ8uYCZPXO?H(bz=jW-^hLJpoVpEH5r2D+j3 zSM)^`k{y%L=;jY63949hk*L%JMx;wZ zV8!sH;yOV#^gXgFCE(cTw$=rQLQwGaVg`m&3oz$}pb}it6)Y#MZ$ut)_mM;Uan|Q; z3t938F?I0a47VRQc1Ns5n*jsVO-N8X%**d8jTL<-v zivS|WSkXii2lc_8updl2nl_R)ng*-GTE^*3`NMs#wEwmE^Z%6fr;9T>9!c_mCC@Am zR%}%g<$PM_;~9*r=WZ-Mz$MdCf{3&DfURHD6B8Yg*(XM2pZfn75Hl~|ugtet@^TmM zzh7N%N;qXt9OXC}S8E}ylW?rR8Z=;+8H4us3u;lNO8T$b5DqL%hC z^TY2x$gpiSy6bI))`YO6g$1F%ErAJcIG}W546}Mi0 zoEoDPoN?Ao{G1YUU_3HMXTCV>a;cc8@%PX+apkjMd0Jd}6DN35k@)#3hU(XBcGsp& zA_(eyEjM*V|8WvRt;$wiGR&$n+E-jIv&hlNeWAA;3PkR?ww;X(m9Ui6KP-vr|jhagjl0e(;u{$2!=rz1!tBH~>f?YQ&rbmD-AZ6fuTe>Q&gx^=#b z+sm`=$+1(IyS$QFsjlr?U;J@EZU8r-gxJTq@9Xf2`{6u5`i+Z(m)w>b<#elMh=guf8g0zF+W-JBEqeNcpd)Mmvq=OW*wL zqLebnS!o^>|H}$2xDK6xj!q<%jl{QZq9H@+`zkKO)kROGYUOlA2? zIzfJfDsJ%Br0LYUw7@jAw2x9Jr@yIY)OEb4@x^JYRkS-(suQ~xrKB;q zvEb%cNzGN~rUl59lB$y$$CK0FSs$pCjR^1iIB}@wm7cOG*B8C$Q?}V=KC$m z<%i3vK#u=EU--K*oB~f}Cjfr*ZiY|!cTfEwvh<*Js#4sXS3u{2>{A~sn$M0R72K0s zI8=ie-=(pm!l60v`mL)1?}Fk74?P)@_S0yx*Ft1}$PujNPeEhOtqs+|UoAO!paBmz z*n{$p_B$VZ?Ft_}lTexwO1rz%1oDary!i5l`)~&L!`;!B2Zfl!H~At2ul!5 zJtDgq!>XA@S&H=0GMf|VQoQ~R|2PtL>2&#Y+mF!JmkS7lqZ_pjoAU$dNwWS zO0&X7VwQs2n$}0Yk_JKk{XF_Lm2E1g- z=Y1U)uQPzwSV370dXs0>&JDEr2;vonwvYkBlul3`ii69q0_!e{e-?M>97SlbAw$}h zFYsJp(r}zPkg5@$##sP=NVtJHxpD=^`y*_VdTY?LV9LcfvSFi9HxV`3U@BCC$RK8d zW_R;e$^~E#Y`G9^+{!X>+}=dMj*K`=-QmMv8l3MaSe7-8&=_qt@VNx&WlZQ90BNV;w2nz>o8@6tD9MJe=-*!~dmG*n_gj{LQXkF8{(2#7 zl`Mu2K0vGu_IMVyTK6nM`|~X7t7%zw{45S^`BM>I`Au`Z^)XaGU3J#Q0JRO!Pk)1< zse0?JvmQFC3r*Kcd-b95dg!6H1ufiv<8{p2JL+eUybi6-Y;6tLguk^_$$0h1VylXhhE_c(^)D@3!>j9uBbt==Bc(c(rftQ_by<(>>?a QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/esphome/dashboard/static/fonts/material-icons/README.md b/esphome/dashboard/static/fonts/material-icons/README.md deleted file mode 100644 index 34d980de08..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/README.md +++ /dev/null @@ -1,12 +0,0 @@ -The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: - -```html - -``` - -Read more in our full usage guide: -http://google.github.io/material-design-icons/#icon-font-for-the-web - -Source: -https://github.com/google/material-design-icons diff --git a/esphome/dashboard/static/fonts/material-icons/material-icons.css b/esphome/dashboard/static/fonts/material-icons/material-icons.css deleted file mode 100644 index 51f2e0a0d1..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/material-icons.css +++ /dev/null @@ -1,34 +0,0 @@ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url(MaterialIcons-Regular.woff2) format('woff2'), - url(MaterialIcons-Regular.woff) format('woff'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} diff --git a/esphome/dashboard/static/images/favicon.ico b/esphome/dashboard/static/images/favicon.ico deleted file mode 100644 index 88dcd7e2d1ad8943943707409de2a8eb8089924e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHO2XvHG7M_qw4IPx0P_t5`NT?e6;lL4)r58cM(m_Ckh(dsaAOsc^QR*rPNOLVq z0?LvgQVao%aE!VbMPLQXuAzm5natbod-LD%&(BOUV{kpYC-0nZ?tS;Q``-V`ePwL6 z09&A~d2<_Rgl%U7n=RUAvqeN0{_-~4*N~M5W%z_3n{5dqL?I56*hT}CYggYzihqk+ zptuE!Tj2ks1p+Mfpx=|s678-y47aB9CbQ=CYy2gUh@cB}5$zZKMJAN;g5rA%Gv{v< zb8kq`o;?HGv}qH@+)9)vq1&!==gz?~F)<;Og@lA?*~1S%9NMEt53EeuX<3I39m0kU z8zws21`Qfif--ZQ?%lfwF)d|*fq^=m5ndD`Wkw>C)TmLEy$N)!TD7W5NlD3V-MTep(}0=yjw(4h zIk$4<%9M2m_QuA>s;yhM(tiV}3D6a(4gmoHqJL6CLV|+JjfjX~8U2Bb)YMdU@!~~= zcvs->U7Z(<%R7OTdUg^@M zDeD5*kZ)o7GnyyII?*xxQGj{o8I-)!#MAGqjOnat&3@rYIlkzm54e1;Uy%)f_ZxuY zFcbLNgiOpc49i=)cCGsI%P&=0TADg@=8PMsPoGwVRc7{+x5n`#lf2HIJ7=$3w=Uab z20$lsnak(;6Z$QHqC2awALl%)0lZ?(Un|4fukCvENWxqf9_ zcYt>%u*bk&&>g@>z;_iZR;&kFp#Dy$Q}?HZ3m2-sefz4RLx(EPHC9mGuU|iP>eMM6 zpWo4ollCu2f2kAa<4XY7&F6r*2IhhGsZ*y;WzcKBw7=136~;Wj{apwO3evJon>J~+ z*^j(9fAn91vS<^m0yYBc4Qv9PShHr$s-Okx&-Q1(YTC4^)(>M|QCBNMLqm1=#*G{A zh5piinn1s$zzPE^L0P9=z!hKmkF=q$?Z0{R=6k7s6!eS-78{`M^MN;j0`-^v`{}2j zs_E0GtLoLO>-NQ(sVUOuqtdTnn<}m+GH#^)ChAZBc^>6l16~ET0$YIBfo;Ha^dHWj z9{Z2zpPrtsN|h?5brl_vCZF!&hdrX@BS((Z>Pj9a-O2i+|1#u#0f+@S-nnKFPXRp& z8^2OM>tNWDN_0f}e031{h!G>SxHnQj&bz?hfPDaM^ml-hfKT(MtO;EID^;qb zbrxNbhxYTka9q>BWy=<=e-Y;&S-+|y&tbp_16;dkM=S!a`Z|6^7f*Gd?yQet7pr?T zer5h&2tC;@p8y#KXb=4ZcW}{HKL@BVoY(#%{a@O)kn(jf^=5n@+FQq$?*xxO`kUMTF8yoQuC4q3uhHL^ zI-28n(BGH+7j2)nb?fK1v-S8j?6;pi?7z4Xa~aORC}G{4G2AMn(QGxGt$E4*t04Vl z{x1*R*~WAtbMqxjmgITUclGMkQ&CY-PM$r`&TQ7Knc}$u&r+~PYhL6SGm7yU&Um!z zm=5W+jPgc}8fh8NA0#iPVOoUSjWeb(R0PgEE?pAeesz6NhiF94#QC0DvSf(^XC#~# zUkASD+{n2dJkOjqpR1dSf}Tq-qN4V?TV=R9K*bZW9^hYl2)u(!Jd+9oDFcFpK%nK z=klUB(SJMJ2WzL@ur=Eu)3l{amn!Z@z<-URT7qVCuRn3(M9R1q?S&;=!N$(Pc`aqL zfVf8-aL%QB4EPbq0%8F= z?m=1p1)wcJw*znh$AL-!$J@sM&pqA%=sE%y0j8Y|&`H@YUlD&BG-wbC`?8{ujqY=3 z+p=iGa%LI&Pz8+XQX-3wj}L$ySgA#e7F^%8Yu>zhFlB4jta0~W%+;k}b5t;*cc#|=2#+7hudHmNAi_$U#T+mQnU`t`b)i>tz7lIN9`7cVj_S}fcu>mcL2rif3*e7Hh|TZ zu!cz;y~!oqS1R@u^Yg?rAIb^3f_Sq|uGqf)h9a8m4{4u<7|FQr;OM4fxx&*=Udkgc zdlZKG;-o&!v0uFkn>P`OYT*3;6!wFkgRccUdk4a}FKB`L^ygqtJHd0$Hh?_#Ky$&j zg3X+TJHp-IB~P$-Io8h&-0MhP;`Z&^rxq<*#B%0wAGKr04h4Sx%$YN<9yxM^{Hku< zx~1`sqFlLhyYaqZFL}J1Nk!O;$B!RZ*aP1N9|_oycR_kEif%XD z>%=<{o@-x0p62pJ2e!#8z-mCs=YDq5oH=uJ`9}Ta?p)t(@r=bIni z)6kjDM4j2$*{uInW_%mb-`z8#dfkcf{rUAT7sSqt2f{PclxMG_8*J#*az7Lub}@Q0llmJnXmMH<4jMN z%kt$O=g^@;jOI)E(*Ep!XMlyk0^kMA=c7SKTINOWM*bE znm^ya4$68PprcMfn){{y#?-ys*fphK7ojY})w2j0pr2k9(Yools8f9`WlC@_T z^MAA$eDm)=QAQb4Ia2@1ka4Z1GnYSf`0(Kh>!*WvTZa!HRy^Yup7RXPF;h}f6#E?4 zY4UulN1ihJ5#PypunDa5udBqnf3;pX)4mTuX`Ey6*5@|(bevD;wQJXoeJ;Iz{rb0f z-VHtj=XpQ&?%kX7&;_iW7s%u6?gxZ@&bJ6ypBay9E1lHa<#YY!Taz)Cqz^tr-LdA< z{+MsFKjKaH$7dFXu-XaVDAz`;B+^x=zU3 zQ~eR_)$x8r#zZM3FU^>sv_s1oJnhSH@`ejXxY(TKjJOi!50!SH&g4nWJo6H{@YXbx zOFHuYI&)fYJo99jIB&d!7t`Ol1zc04hu_rbPgJTcTrIGLW6+0w3uG;@gR%p+00Q(c7@2jz7UVFV-2F>6O4P$5ZrMFM@B}5V15c|)v8qx=A%%n4!DQW;}UmXW?$_ChX#yxC8u&cYn}fAQKvH6w<#1el_-RCuk=Cc7S`EJgn7j;N#@-y*%FX zb5AC20PMTJz!Q)C-}_wa;T+h1I(c`BIo65xaKiTC+IxrTaql=s5`zvh($60fr2v>xd0|;nm!?KKtym*MV&CIk0nXfzQFdH3#$>_Z?WXMlrrPq}TlickIzv-_M}W z?23trc?*5!Aok*KVx3QfJ&*+ZU>n}(o<=&k2V@p{T#&)}Kna{(lth0FAz(X%;T$6b z`G=y+P_$<-_7Ei*w*Xhz*m0NVDG!c~!knaYoh74_;49}~1ZDw;fpi1pDHAY`In93n DM+@@2 diff --git a/esphome/dashboard/static/js/esphome.js b/esphome/dashboard/static/js/esphome.js deleted file mode 100644 index e861f169df..0000000000 --- a/esphome/dashboard/static/js/esphome.js +++ /dev/null @@ -1,1021 +0,0 @@ -'use strict'; - -// Document Ready -$(document).ready(function () { - M.AutoInit(document.body); - nodeGrid(); - startAceWebsocket(); - fixNavbarHeight(); -}); - -// WebSocket URL Helper -const loc = window.location; -const wsLoc = new URL("./", `${loc.protocol}//${loc.host}${loc.pathname}`); -wsLoc.protocol = 'ws:'; -if (loc.protocol === "https:") { - wsLoc.protocol = 'wss:'; -} -const wsUrl = wsLoc.href; - -/** - * Fix NavBar height - */ -const fixNavbarHeight = () => { - const fixFunc = () => { - const sel = $(".select-wrapper"); - $(".navbar-fixed").css("height", (sel.position().top + sel.outerHeight()) + "px"); - } - $(window).resize(fixFunc); - fixFunc(); -} - -/** - * Dashboard Dynamic Grid - */ -const nodeGrid = () => { - const nodeCount = document.querySelectorAll("#nodes .card").length; - const nodeGrid = document.querySelector("#nodes #grid"); - - if (nodeCount <= 3) { - nodeGrid.classList.add("grid-1-col"); - } else if (nodeCount <= 6) { - nodeGrid.classList.add("grid-2-col"); - } else { - nodeGrid.classList.add("grid-3-col"); - } -} - -/** - * Online/ Offline Status Indication - */ - -let isFetchingPing = false; - -const fetchPing = () => { - if (isFetchingPing) { - return; - } - - isFetchingPing = true; - - fetch(`./ping`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - for (let filename in response) { - let node = document.querySelector(`#nodes .card[data-filename="${filename}"]`); - - if (node === null) { - continue; - } - - let status = response[filename]; - let className; - - if (status === null) { - className = 'status-unknown'; - } else if (status === true) { - className = 'status-online'; - node.setAttribute('data-last-connected', Date.now().toString()); - } else if (node.hasAttribute('data-last-connected')) { - const attr = parseInt(node.getAttribute('data-last-connected')); - if (Date.now() - attr <= 5000) { - className = 'status-not-responding'; - } else { - className = 'status-offline'; - } - } else { - className = 'status-offline'; - } - - if (node.classList.contains(className)) { - continue; - } - - node.classList.remove('status-unknown', 'status-online', 'status-offline', 'status-not-responding'); - node.classList.add(className); - } - - isFetchingPing = false; - }); -}; -setInterval(fetchPing, 2000); -fetchPing(); - -/** - * Log Color Parsing - */ - -const initializeColorState = () => { - return { - bold: false, - italic: false, - underline: false, - strikethrough: false, - foregroundColor: false, - backgroundColor: false, - carriageReturn: false, - secret: false, - }; -}; - -const colorReplace = (pre, state, text) => { - const re = /(?:\033|\\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; - let i = 0; - - if (state.carriageReturn) { - if (text !== "\n") { - // don't remove if \r\n - pre.removeChild(pre.lastChild); - } - state.carriageReturn = false; - } - - if (text.includes("\r")) { - state.carriageReturn = true; - } - - const lineSpan = document.createElement("span"); - lineSpan.classList.add("line"); - pre.appendChild(lineSpan); - - const addSpan = (content) => { - if (content === "") - return; - - const span = document.createElement("span"); - if (state.bold) span.classList.add("log-bold"); - if (state.italic) span.classList.add("log-italic"); - if (state.underline) span.classList.add("log-underline"); - if (state.strikethrough) span.classList.add("log-strikethrough"); - if (state.secret) span.classList.add("log-secret"); - if (state.foregroundColor !== null) span.classList.add(`log-fg-${state.foregroundColor}`); - if (state.backgroundColor !== null) span.classList.add(`log-bg-${state.backgroundColor}`); - span.appendChild(document.createTextNode(content)); - lineSpan.appendChild(span); - - if (state.secret) { - const redacted = document.createElement("span"); - redacted.classList.add("log-secret-redacted"); - redacted.appendChild(document.createTextNode("[redacted]")); - lineSpan.appendChild(redacted); - } - }; - - - while (true) { - const match = re.exec(text); - if (match === null) - break; - - const j = match.index; - addSpan(text.substring(i, j)); - i = j + match[0].length; - - if (match[1] === undefined) continue; - - for (const colorCode of match[1].split(";")) { - switch (parseInt(colorCode)) { - case 0: - // reset - state.bold = false; - state.italic = false; - state.underline = false; - state.strikethrough = false; - state.foregroundColor = null; - state.backgroundColor = null; - state.secret = false; - break; - case 1: - state.bold = true; - break; - case 3: - state.italic = true; - break; - case 4: - state.underline = true; - break; - case 5: - state.secret = true; - break; - case 6: - state.secret = false; - break; - case 9: - state.strikethrough = true; - break; - case 22: - state.bold = false; - break; - case 23: - state.italic = false; - break; - case 24: - state.underline = false; - break; - case 29: - state.strikethrough = false; - break; - case 30: - state.foregroundColor = "black"; - break; - case 31: - state.foregroundColor = "red"; - break; - case 32: - state.foregroundColor = "green"; - break; - case 33: - state.foregroundColor = "yellow"; - break; - case 34: - state.foregroundColor = "blue"; - break; - case 35: - state.foregroundColor = "magenta"; - break; - case 36: - state.foregroundColor = "cyan"; - break; - case 37: - state.foregroundColor = "white"; - break; - case 39: - state.foregroundColor = null; - break; - case 41: - state.backgroundColor = "red"; - break; - case 42: - state.backgroundColor = "green"; - break; - case 43: - state.backgroundColor = "yellow"; - break; - case 44: - state.backgroundColor = "blue"; - break; - case 45: - state.backgroundColor = "magenta"; - break; - case 46: - state.backgroundColor = "cyan"; - break; - case 47: - state.backgroundColor = "white"; - break; - case 40: - case 49: - state.backgroundColor = null; - break; - } - } - } - addSpan(text.substring(i)); - if (pre.scrollTop + 56 >= (pre.scrollHeight - pre.offsetHeight)) { - // at bottom - pre.scrollTop = pre.scrollHeight; - } -}; - -/** - * Serial Port Selection - */ - -const portSelect = document.querySelector('.nav-wrapper select'); -let ports = []; - -const fetchSerialPorts = (begin = false) => { - fetch(`./serial-ports`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - if (ports.length === response.length) { - let allEqual = true; - for (let i = 0; i < response.length; i++) { - if (ports[i].port !== response[i].port) { - allEqual = false; - break; - } - } - if (allEqual) - return; - } - const hasNewPort = response.length >= ports.length; - - ports = response; - - const inst = M.FormSelect.getInstance(portSelect); - if (inst !== undefined) { - inst.destroy(); - } - - portSelect.innerHTML = ""; - const prevSelected = getUploadPort(); - for (let i = 0; i < response.length; i++) { - const val = response[i]; - if (val.port === prevSelected) { - portSelect.innerHTML += ``; - } else { - portSelect.innerHTML += ``; - } - } - - M.FormSelect.init(portSelect, {}); - if (!begin && hasNewPort) - M.toast({ html: "Discovered new serial port." }); - }); -}; - -const getUploadPort = () => { - const inst = M.FormSelect.getInstance(portSelect); - if (inst === undefined) { - return "OTA"; - } - - inst._setSelectedStates(); - return inst.getSelectedValues()[0]; -}; -setInterval(fetchSerialPorts, 5000); -fetchSerialPorts(true); - -/** - * Log Elements - */ - -// Log Modal Class -class LogModal { - constructor({ - name, - onPrepare = (modalElement, config) => { }, - onProcessExit = (modalElement, code) => { }, - onSocketClose = (modalElement) => { }, - dismissible = true - }) { - this.modalId = `js-${name}-modal`; - this.dataAction = `${name}`; - this.wsUrl = `${wsUrl}${name}`; - this.dismissible = dismissible; - this.activeFilename = null; - - this.modalElement = document.getElementById(this.modalId); - this.nodeFilenameElement = document.querySelector(`#${this.modalId} #js-node-filename`); - this.logElement = document.querySelector(`#${this.modalId} #js-log-area`); - this.onPrepare = onPrepare; - this.onProcessExit = onProcessExit; - this.onSocketClose = onSocketClose; - } - - setup() { - const boundOnPress = this._onPress.bind(this); - document.querySelectorAll(`[data-action="${this.dataAction}"]`).forEach((button) => { - button.addEventListener('click', boundOnPress); - }); - } - - _setupModalInstance() { - this.modalInstance = M.Modal.init(this.modalElement, { - onOpenStart: this._onOpenStart.bind(this), - onCloseStart: this._onCloseStart.bind(this), - dismissible: this.dismissible - }) - } - - _onOpenStart() { - document.addEventListener('keydown', this._boundKeydown); - } - - _onCloseStart() { - document.removeEventListener('keydown', this._boundKeydown); - this.activeSocket.close(); - } - - open(event) { - this._onPress(event); - } - - _onPress(event) { - this.activeFilename = event.target.getAttribute('data-filename'); - - this._setupModalInstance(); - this.nodeFilenameElement.innerHTML = this.activeFilename; - - this.logElement.innerHTML = ""; - const colorLogState = initializeColorState(); - - this.onPrepare(this.modalElement, this.activeFilename); - - let stopped = false; - - this.modalInstance.open(); - - const socket = new WebSocket(this.wsUrl); - this.activeSocket = socket; - socket.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - if (data.event === "line") { - colorReplace(this.logElement, colorLogState, data.data); - } else if (data.event === "exit") { - this.onProcessExit(this.modalElement, data.code); - stopped = true; - } - }); - - socket.addEventListener('open', () => { - const msg = JSON.stringify(this._encodeSpawnMessage(this.activeFilename)); - socket.send(msg); - }); - - socket.addEventListener('close', () => { - if (!stopped) { - this.onSocketClose(this.modalElement); - } - }); - } - - _onKeyDown(event) { - // Close on escape key - if (event.keyCode === 27) { - this.modalInstance.close(); - } - } - - _encodeSpawnMessage(filename) { - return { - type: 'spawn', - configuration: filename, - port: getUploadPort(), - }; - } -} - -// Logs Modal -const logsModal = new LogModal({ - name: "logs", - - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("[data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - } - modalElem.querySelector("data-action='stop-logs'").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - } -}) - -logsModal.setup(); - -// Upload Modal -const uploadModal = new LogModal({ - name: "upload", - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.remove('disabled'); - } - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - }, - - dismissible: false -}) - -uploadModal.setup(); - -const downloadAfterUploadButton = document.querySelector("#js-upload-modal [data-action='download-binary']"); -downloadAfterUploadButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(uploadModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Validate Modal -const validateModal = new LogModal({ - name: 'validate', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-validate-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: `${validateModal.activeFilename} is valid 👍`, - displayLength: 10000, - }); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.remove('disabled'); - } else { - M.toast({ - html: `${validateModal.activeFilename} is invalid 😕`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -validateModal.setup(); - -// Compile Modal -const compileModal = new LogModal({ - name: 'compile', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -compileModal.setup(); - -const downloadAfterCompileButton = document.querySelector("#js-compile-modal [data-action='download-binary']"); -downloadAfterCompileButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(compileModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Clean MQTT Modal -const cleanMqttModal = new LogModal({ - name: 'clean-mqtt', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanMqttModal.setup(); - -// Clean Build Files Modal -const cleanModal = new LogModal({ - name: 'clean', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanModal.setup(); - -// Update All Modal -const updateAllModal = new LogModal({ - name: 'update-all', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-update-all-modal #js-node-filename").style.visibility = "hidden"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - downloadButton.classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -updateAllModal.setup(); - -/** - * Node Editing - */ - -let editorActiveFilename = null; -let editorActiveSecrets = false; -let editorActiveWebSocket = null; -let editorValidationScheduled = false; -let editorValidationRunning = false; - -// Setup Editor -const editorElement = document.querySelector("#js-editor-modal #js-editor-area"); -const editor = ace.edit(editorElement); - -editor.setOptions({ - highlightActiveLine: true, - showPrintMargin: true, - useSoftTabs: true, - tabSize: 2, - useWorker: false, - theme: 'ace/theme/dreamweaver', - mode: 'ace/mode/yaml' -}); - -editor.commands.addCommand({ - name: 'saveCommand', - bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, - exec: function () { - saveFile(editorActiveFilename); - }, - readOnly: false -}); - -// Edit Button Listener -document.querySelectorAll("[data-action='edit']").forEach((button) => { - button.addEventListener('click', (event) => { - - editorActiveFilename = event.target.getAttribute("data-filename"); - const filenameField = document.querySelector("#js-editor-modal #js-node-filename"); - filenameField.innerHTML = editorActiveFilename; - - const saveButton = document.querySelector("#js-editor-modal [data-action='save']"); - const uploadButton = document.querySelector("#js-editor-modal [data-action='upload']"); - const closeButton = document.querySelector("#js-editor-modal [data-action='close']"); - saveButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('onClick', `saveFile("${editorActiveFilename}")`); - if (editorActiveFilename === "secrets.yaml") { - uploadButton.classList.add("disabled"); - editorActiveSecrets = true; - } else { - uploadButton.classList.remove("disabled"); - editorActiveSecrets = false; - } - closeButton.setAttribute('data-filename', editorActiveFilename); - - const loadingIndicator = document.querySelector("#js-editor-modal #js-loading-indicator"); - const editorArea = document.querySelector("#js-editor-modal #js-editor-area"); - - loadingIndicator.style.display = "block"; - editorArea.style.display = "none"; - - editor.setOption('readOnly', true); - fetch(`./edit?configuration=${editorActiveFilename}`, { credentials: "same-origin" }) - .then(res => res.text()).then(response => { - editor.setValue(response, -1); - editor.setOption('readOnly', false); - loadingIndicator.style.display = "none"; - editorArea.style.display = "block"; - }); - editor.focus(); - - const editModalElement = document.getElementById("js-editor-modal"); - const editorModal = M.Modal.init(editModalElement, { - onOpenStart: function () { - editorModalOnOpen() - }, - onCloseStart: function () { - editorModalOnClose() - }, - dismissible: false - }) - - editorModal.open(); - - }); -}); - -// Editor On Open -const editorModalOnOpen = () => { - return -} - -// Editor On Close -const editorModalOnClose = () => { - editorActiveFilename = null; -} - -// Editor WebSocket Validation -const startAceWebsocket = () => { - editorActiveWebSocket = new WebSocket(`${wsUrl}ace`); - - editorActiveWebSocket.addEventListener('message', (event) => { - const raw = JSON.parse(event.data); - if (raw.event === "line") { - const msg = JSON.parse(raw.data); - if (msg.type === "result") { - const arr = []; - - for (const v of msg.validation_errors) { - let o = { - text: v.message, - type: 'error', - row: 0, - column: 0 - }; - if (v.range != null) { - o.row = v.range.start_line; - o.column = v.range.start_col; - } - arr.push(o); - } - for (const v of msg.yaml_errors) { - arr.push({ - text: v.message, - type: 'error', - row: 0, - column: 0 - }); - } - - editor.session.setAnnotations(arr); - - if (arr.length) { - document.querySelector("#js-editor-modal [data-action='upload']").classList.add('disabled'); - } else { - document.querySelector("#js-editor-modal [data-action='upload']").classList.remove('disabled'); - } - - editorValidationRunning = false; - } else if (msg.type === "read_file") { - sendAceStdin({ - type: 'file_response', - content: editor.getValue() - }); - } - } - }) - - editorActiveWebSocket.addEventListener('open', () => { - const msg = JSON.stringify({ type: 'spawn' }); - editorActiveWebSocket.send(msg); - }); - - editorActiveWebSocket.addEventListener('close', () => { - editorActiveWebSocket = null; - setTimeout(startAceWebsocket, 5000); - }); -}; - -const sendAceStdin = (data) => { - let send = JSON.stringify({ - type: 'stdin', - data: JSON.stringify(data) + '\n', - }); - editorActiveWebSocket.send(send); -}; - -const debounce = (func, wait) => { - let timeout; - return function () { - let context = this, args = arguments; - let later = function () { - timeout = null; - func.apply(context, args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -editor.session.on('change', debounce(() => { - editorValidationScheduled = !editorActiveSecrets; -}, 250)); - -setInterval(() => { - if (!editorValidationScheduled || editorValidationRunning) - return; - if (editorActiveWebSocket == null) - return; - - sendAceStdin({ - type: 'validate', - file: editorActiveFilename - }); - editorValidationRunning = true; - editorValidationScheduled = false; -}, 100); - -// Save File -const saveFile = (filename) => { - const extensionRegex = new RegExp("(?:\.([^.]+))?$"); - - if (filename.match(extensionRegex)[0] !== ".yaml") { - M.toast({ - html: `❌ File ${filename} cannot be saved as it is not a YAML file!`, - displayLength: 10000 - }); - return; - } - - fetch(`./edit?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - body: editor.getValue() - }) - .then((response) => { - response.text(); - }) - .then(() => { - M.toast({ - html: `✅ Saved ${filename}`, - displayLength: 10000 - }); - }) - .catch((error) => { - M.toast({ - html: `❌ An error occured saving ${filename}`, - displayLength: 10000 - }); - }) -} - -document.querySelectorAll("[data-action='save']").forEach((btn) => { - btn.addEventListener("click", (e) => { - saveFile(editorActiveFilename); - }); -}); - -// Delete Node -document.querySelectorAll("[data-action='delete']").forEach((btn) => { - btn.addEventListener("click", (e) => { - const filename = e.target.getAttribute("data-filename"); - - fetch(`./delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - const toastHtml = `🗑️ Deleted ${filename} - `; - const toast = M.toast({ - html: toastHtml, - displayLength: 10000 - }); - const undoButton = toast.el.querySelector('.toast-action'); - - document.querySelector(`.card[data-filename="${filename}"]`).remove(); - - undoButton.addEventListener('click', () => { - fetch(`./undo-delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - window.location.reload(false); - }); - }); - }); - - }); -}); - -/** - * Wizard - */ - -const wizardTriggerElement = document.querySelector("[data-action='wizard']"); -const wizardModal = document.getElementById("js-wizard-modal"); -const wizardCloseButton = document.querySelector("[data-action='wizard-close']"); - -const wizardStepper = document.querySelector('#js-wizard-modal .stepper'); -const wizardStepperInstace = new MStepper(wizardStepper, { - firstActive: 0, - stepTitleNavigation: false, - autoFocusInput: true, - showFeedbackLoader: true, -}) - -const startWizard = () => { - M.Modal.init(wizardModal, { - dismissible: false - }).open(); -} - -document.querySelectorAll("[data-action='wizard']").forEach((btn) => { - btn.addEventListener("click", (event) => { - startWizard(); - }) -}); - -jQuery.validator.addMethod("nospaces", (value, element) => { - return value.indexOf(' ') < 0; -}, "Name cannot contain any spaces!"); - -jQuery.validator.addMethod("nounderscores", (value, element) => { - return value.indexOf('_') < 0; -}, "Name cannot contain underscores!"); - -jQuery.validator.addMethod("lowercase", (value, element) => { - return value === value.toLowerCase(); -}, "Name must be all lower case!"); diff --git a/esphome/dashboard/static/js/vendor/ace/ace.js b/esphome/dashboard/static/js/vendor/ace/ace.js deleted file mode 100644 index 0a66043d67..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ace.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { function o(n) { var i = e; n && (e[n] || (e[n] = {}), i = e[n]); if (!i.define || !i.define.packaged) t.original = i.define, i.define = t, i.define.packaged = !0; if (!i.require || !i.require.packaged) r.original = i.require, i.require = r, i.require.packaged = !0 } var ACE_NAMESPACE = "", e = function () { return this }(); !e && typeof window != "undefined" && (e = window); if (!ACE_NAMESPACE && typeof requirejs != "undefined") return; var t = function (e, n, r) { if (typeof e != "string") { t.original ? t.original.apply(this, arguments) : (console.error("dropping module because define wasn't a string."), console.trace()); return } arguments.length == 2 && (r = n), t.modules[e] || (t.payloads[e] = r, t.modules[e] = null) }; t.modules = {}, t.payloads = {}; var n = function (e, t, n) { if (typeof t == "string") { var i = s(e, t); if (i != undefined) return n && n(), i } else if (Object.prototype.toString.call(t) === "[object Array]") { var o = []; for (var u = 0, a = t.length; u < a; ++u) { var f = s(e, t[u]); if (f == undefined && r.original) return; o.push(f) } return n && n.apply(null, o) || !0 } }, r = function (e, t) { var i = n("", e, t); return i == undefined && r.original ? r.original.apply(this, arguments) : i }, i = function (e, t) { if (t.indexOf("!") !== -1) { var n = t.split("!"); return i(e, n[0]) + "!" + i(e, n[1]) } if (t.charAt(0) == ".") { var r = e.split("/").slice(0, -1).join("/"); t = r + "/" + t; while (t.indexOf(".") !== -1 && s != t) { var s = t; t = t.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "") } } return t }, s = function (e, r) { r = i(e, r); var s = t.modules[r]; if (!s) { s = t.payloads[r]; if (typeof s == "function") { var o = {}, u = { id: r, uri: "", exports: o, packaged: !0 }, a = function (e, t) { return n(r, e, t) }, f = s(a, o, u); o = f || u.exports, t.modules[r] = o, delete t.payloads[r] } s = t.modules[r] = o || s } return s }; o(ACE_NAMESPACE) })(), define("ace/lib/regexp", ["require", "exports", "module"], function (e, t, n) { "use strict"; function o(e) { return (e.global ? "g" : "") + (e.ignoreCase ? "i" : "") + (e.multiline ? "m" : "") + (e.extended ? "x" : "") + (e.sticky ? "y" : "") } function u(e, t, n) { if (Array.prototype.indexOf) return e.indexOf(t, n); for (var r = n || 0; r < e.length; r++)if (e[r] === t) return r; return -1 } var r = { exec: RegExp.prototype.exec, test: RegExp.prototype.test, match: String.prototype.match, replace: String.prototype.replace, split: String.prototype.split }, i = r.exec.call(/()??/, "")[1] === undefined, s = function () { var e = /^/g; return r.test.call(e, ""), !e.lastIndex }(); if (s && i) return; RegExp.prototype.exec = function (e) { var t = r.exec.apply(this, arguments), n, a; if (typeof e == "string" && t) { !i && t.length > 1 && u(t, "") > -1 && (a = RegExp(this.source, r.replace.call(o(this), "g", "")), r.replace.call(e.slice(t.index), a, function () { for (var e = 1; e < arguments.length - 2; e++)arguments[e] === undefined && (t[e] = undefined) })); if (this._xregexp && this._xregexp.captureNames) for (var f = 1; f < t.length; f++)n = this._xregexp.captureNames[f - 1], n && (t[n] = t[f]); !s && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex-- } return t }, s || (RegExp.prototype.test = function (e) { var t = r.exec.call(this, e); return t && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex--, !!t }) }), define("ace/lib/es5-shim", ["require", "exports", "module"], function (e, t, n) { function r() { } function w(e) { try { return Object.defineProperty(e, "sentinel", {}), "sentinel" in e } catch (t) { } } function H(e) { return e = +e, e !== e ? e = 0 : e !== 0 && e !== 1 / 0 && e !== -1 / 0 && (e = (e > 0 || -1) * Math.floor(Math.abs(e))), e } function B(e) { var t = typeof e; return e === null || t === "undefined" || t === "boolean" || t === "number" || t === "string" } function j(e) { var t, n, r; if (B(e)) return e; n = e.valueOf; if (typeof n == "function") { t = n.call(e); if (B(t)) return t } r = e.toString; if (typeof r == "function") { t = r.call(e); if (B(t)) return t } throw new TypeError } Function.prototype.bind || (Function.prototype.bind = function (t) { var n = this; if (typeof n != "function") throw new TypeError("Function.prototype.bind called on incompatible " + n); var i = u.call(arguments, 1), s = function () { if (this instanceof s) { var e = n.apply(this, i.concat(u.call(arguments))); return Object(e) === e ? e : this } return n.apply(t, i.concat(u.call(arguments))) }; return n.prototype && (r.prototype = n.prototype, s.prototype = new r, r.prototype = null), s }); var i = Function.prototype.call, s = Array.prototype, o = Object.prototype, u = s.slice, a = i.bind(o.toString), f = i.bind(o.hasOwnProperty), l, c, h, p, d; if (d = f(o, "__defineGetter__")) l = i.bind(o.__defineGetter__), c = i.bind(o.__defineSetter__), h = i.bind(o.__lookupGetter__), p = i.bind(o.__lookupSetter__); if ([1, 2].splice(0).length != 2) if (!function () { function e(e) { var t = new Array(e + 2); return t[0] = t[1] = 0, t } var t = [], n; t.splice.apply(t, e(20)), t.splice.apply(t, e(26)), n = t.length, t.splice(5, 0, "XXX"), n + 1 == t.length; if (n + 1 == t.length) return !0 }()) Array.prototype.splice = function (e, t) { var n = this.length; e > 0 ? e > n && (e = n) : e == void 0 ? e = 0 : e < 0 && (e = Math.max(n + e, 0)), e + t < n || (t = n - e); var r = this.slice(e, e + t), i = u.call(arguments, 2), s = i.length; if (e === n) s && this.push.apply(this, i); else { var o = Math.min(t, n - e), a = e + o, f = a + s - o, l = n - a, c = n - o; if (f < a) for (var h = 0; h < l; ++h)this[f + h] = this[a + h]; else if (f > a) for (h = l; h--;)this[f + h] = this[a + h]; if (s && e === c) this.length = c, this.push.apply(this, i); else { this.length = c + s; for (h = 0; h < s; ++h)this[e + h] = i[h] } } return r }; else { var v = Array.prototype.splice; Array.prototype.splice = function (e, t) { return arguments.length ? v.apply(this, [e === void 0 ? 0 : e, t === void 0 ? this.length - e : t].concat(u.call(arguments, 2))) : [] } } Array.isArray || (Array.isArray = function (t) { return a(t) == "[object Array]" }); var m = Object("a"), g = m[0] != "a" || !(0 in m); Array.prototype.forEach || (Array.prototype.forEach = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = arguments[1], s = -1, o = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError; while (++s < o) s in r && t.call(i, r[s], s, n) }), Array.prototype.map || (Array.prototype.map = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = Array(i), o = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var u = 0; u < i; u++)u in r && (s[u] = t.call(o, r[u], u, n)); return s }), Array.prototype.filter || (Array.prototype.filter = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = [], o, u = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var f = 0; f < i; f++)f in r && (o = r[f], t.call(u, o, f, n) && s.push(o)); return s }), Array.prototype.every || (Array.prototype.every = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && !t.call(s, r[o], o, n)) return !1; return !0 }), Array.prototype.some || (Array.prototype.some = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && t.call(s, r[o], o, n)) return !0; return !1 }), Array.prototype.reduce || (Array.prototype.reduce = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduce of empty array with no initial value"); var s = 0, o; if (arguments.length >= 2) o = arguments[1]; else do { if (s in r) { o = r[s++]; break } if (++s >= i) throw new TypeError("reduce of empty array with no initial value") } while (!0); for (; s < i; s++)s in r && (o = t.call(void 0, o, r[s], s, n)); return o }), Array.prototype.reduceRight || (Array.prototype.reduceRight = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduceRight of empty array with no initial value"); var s, o = i - 1; if (arguments.length >= 2) s = arguments[1]; else do { if (o in r) { s = r[o--]; break } if (--o < 0) throw new TypeError("reduceRight of empty array with no initial value") } while (!0); do o in this && (s = t.call(void 0, s, r[o], o, n)); while (o--); return s }); if (!Array.prototype.indexOf || [0, 1].indexOf(1, 2) != -1) Array.prototype.indexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = 0; arguments.length > 1 && (i = H(arguments[1])), i = i >= 0 ? i : Math.max(0, r + i); for (; i < r; i++)if (i in n && n[i] === t) return i; return -1 }; if (!Array.prototype.lastIndexOf || [0, 1].lastIndexOf(0, -3) != -1) Array.prototype.lastIndexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = r - 1; arguments.length > 1 && (i = Math.min(i, H(arguments[1]))), i = i >= 0 ? i : r - Math.abs(i); for (; i >= 0; i--)if (i in n && t === n[i]) return i; return -1 }; Object.getPrototypeOf || (Object.getPrototypeOf = function (t) { return t.__proto__ || (t.constructor ? t.constructor.prototype : o) }); if (!Object.getOwnPropertyDescriptor) { var y = "Object.getOwnPropertyDescriptor called on a non-object: "; Object.getOwnPropertyDescriptor = function (t, n) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(y + t); if (!f(t, n)) return; var r, i, s; r = { enumerable: !0, configurable: !0 }; if (d) { var u = t.__proto__; t.__proto__ = o; var i = h(t, n), s = p(t, n); t.__proto__ = u; if (i || s) return i && (r.get = i), s && (r.set = s), r } return r.value = t[n], r } } Object.getOwnPropertyNames || (Object.getOwnPropertyNames = function (t) { return Object.keys(t) }); if (!Object.create) { var b; Object.prototype.__proto__ === null ? b = function () { return { __proto__: null } } : b = function () { var e = {}; for (var t in e) e[t] = null; return e.constructor = e.hasOwnProperty = e.propertyIsEnumerable = e.isPrototypeOf = e.toLocaleString = e.toString = e.valueOf = e.__proto__ = null, e }, Object.create = function (t, n) { var r; if (t === null) r = b(); else { if (typeof t != "object") throw new TypeError("typeof prototype[" + typeof t + "] != 'object'"); var i = function () { }; i.prototype = t, r = new i, r.__proto__ = t } return n !== void 0 && Object.defineProperties(r, n), r } } if (Object.defineProperty) { var E = w({}), S = typeof document == "undefined" || w(document.createElement("div")); if (!E || !S) var x = Object.defineProperty } if (!Object.defineProperty || x) { var T = "Property description must be an object: ", N = "Object.defineProperty called on non-object: ", C = "getters & setters can not be defined on this javascript engine"; Object.defineProperty = function (t, n, r) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(N + t); if (typeof r != "object" && typeof r != "function" || r === null) throw new TypeError(T + r); if (x) try { return x.call(Object, t, n, r) } catch (i) { } if (f(r, "value")) if (d && (h(t, n) || p(t, n))) { var s = t.__proto__; t.__proto__ = o, delete t[n], t[n] = r.value, t.__proto__ = s } else t[n] = r.value; else { if (!d) throw new TypeError(C); f(r, "get") && l(t, n, r.get), f(r, "set") && c(t, n, r.set) } return t } } Object.defineProperties || (Object.defineProperties = function (t, n) { for (var r in n) f(n, r) && Object.defineProperty(t, r, n[r]); return t }), Object.seal || (Object.seal = function (t) { return t }), Object.freeze || (Object.freeze = function (t) { return t }); try { Object.freeze(function () { }) } catch (k) { Object.freeze = function (t) { return function (n) { return typeof n == "function" ? n : t(n) } }(Object.freeze) } Object.preventExtensions || (Object.preventExtensions = function (t) { return t }), Object.isSealed || (Object.isSealed = function (t) { return !1 }), Object.isFrozen || (Object.isFrozen = function (t) { return !1 }), Object.isExtensible || (Object.isExtensible = function (t) { if (Object(t) === t) throw new TypeError; var n = ""; while (f(t, n)) n += "?"; t[n] = !0; var r = f(t, n); return delete t[n], r }); if (!Object.keys) { var L = !0, A = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"], O = A.length; for (var M in { toString: null }) L = !1; Object.keys = function I(e) { if (typeof e != "object" && typeof e != "function" || e === null) throw new TypeError("Object.keys called on a non-object"); var I = []; for (var t in e) f(e, t) && I.push(t); if (L) for (var n = 0, r = O; n < r; n++) { var i = A[n]; f(e, i) && I.push(i) } return I } } Date.now || (Date.now = function () { return (new Date).getTime() }); var _ = " \n\x0b\f\r \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff"; if (!String.prototype.trim) { _ = "[" + _ + "]"; var D = new RegExp("^" + _ + _ + "*"), P = new RegExp(_ + _ + "*$"); String.prototype.trim = function () { return String(this).replace(D, "").replace(P, "") } } var F = function (e) { if (e == null) throw new TypeError("can't convert " + e + " to object"); return Object(e) } }), define("ace/lib/fixoldbrowsers", ["require", "exports", "module", "ace/lib/regexp", "ace/lib/es5-shim"], function (e, t, n) { "use strict"; e("./regexp"), e("./es5-shim"), typeof Element != "undefined" && !Element.prototype.remove && Object.defineProperty(Element.prototype, "remove", { enumerable: !1, writable: !0, configurable: !0, value: function () { this.parentNode && this.parentNode.removeChild(this) } }) }), define("ace/lib/useragent", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.OS = { LINUX: "LINUX", MAC: "MAC", WINDOWS: "WINDOWS" }, t.getOS = function () { return t.isMac ? t.OS.MAC : t.isLinux ? t.OS.LINUX : t.OS.WINDOWS }; var r = typeof navigator == "object" ? navigator : {}, i = (/mac|win|linux/i.exec(r.platform) || ["other"])[0].toLowerCase(), s = r.userAgent || "", o = r.appName || ""; t.isWin = i == "win", t.isMac = i == "mac", t.isLinux = i == "linux", t.isIE = o == "Microsoft Internet Explorer" || o.indexOf("MSAppHost") >= 0 ? parseFloat((s.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]) : parseFloat((s.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]), t.isOldIE = t.isIE && t.isIE < 9, t.isGecko = t.isMozilla = s.match(/ Gecko\/\d+/), t.isOpera = typeof opera == "object" && Object.prototype.toString.call(window.opera) == "[object Opera]", t.isWebKit = parseFloat(s.split("WebKit/")[1]) || undefined, t.isChrome = parseFloat(s.split(" Chrome/")[1]) || undefined, t.isEdge = parseFloat(s.split(" Edge/")[1]) || undefined, t.isAIR = s.indexOf("AdobeAIR") >= 0, t.isAndroid = s.indexOf("Android") >= 0, t.isChromeOS = s.indexOf(" CrOS ") >= 0, t.isIOS = /iPad|iPhone|iPod/.test(s) && !window.MSStream, t.isIOS && (t.isMac = !0), t.isMobile = t.isIOS || t.isAndroid }), define("ace/lib/dom", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./useragent"), i = "http://www.w3.org/1999/xhtml"; t.buildDom = function o(e, t, n) { if (typeof e == "string" && e) { var r = document.createTextNode(e); return t && t.appendChild(r), r } if (!Array.isArray(e)) return e && e.appendChild && t && t.appendChild(e), e; if (typeof e[0] != "string" || !e[0]) { var i = []; for (var s = 0; s < e.length; s++) { var u = o(e[s], t, n); u && i.push(u) } return i } var a = document.createElement(e[0]), f = e[1], l = 1; f && typeof f == "object" && !Array.isArray(f) && (l = 2); for (var s = l; s < e.length; s++)o(e[s], a, n); return l == 2 && Object.keys(f).forEach(function (e) { var t = f[e]; e === "class" ? a.className = Array.isArray(t) ? t.join(" ") : t : typeof t == "function" || e == "value" || e[0] == "$" ? a[e] = t : e === "ref" ? n && (n[t] = a) : t != null && a.setAttribute(e, t) }), t && t.appendChild(a), a }, t.getDocumentHead = function (e) { return e || (e = document), e.head || e.getElementsByTagName("head")[0] || e.documentElement }, t.createElement = function (e, t) { return document.createElementNS ? document.createElementNS(t || i, e) : document.createElement(e) }, t.removeChildren = function (e) { e.innerHTML = "" }, t.createTextNode = function (e, t) { var n = t ? t.ownerDocument : document; return n.createTextNode(e) }, t.createFragment = function (e) { var t = e ? e.ownerDocument : document; return t.createDocumentFragment() }, t.hasCssClass = function (e, t) { var n = (e.className + "").split(/\s+/g); return n.indexOf(t) !== -1 }, t.addCssClass = function (e, n) { t.hasCssClass(e, n) || (e.className += " " + n) }, t.removeCssClass = function (e, t) { var n = e.className.split(/\s+/g); for (; ;) { var r = n.indexOf(t); if (r == -1) break; n.splice(r, 1) } e.className = n.join(" ") }, t.toggleCssClass = function (e, t) { var n = e.className.split(/\s+/g), r = !0; for (; ;) { var i = n.indexOf(t); if (i == -1) break; r = !1, n.splice(i, 1) } return r && n.push(t), e.className = n.join(" "), r }, t.setCssClass = function (e, n, r) { r ? t.addCssClass(e, n) : t.removeCssClass(e, n) }, t.hasCssString = function (e, t) { var n = 0, r; t = t || document; if (r = t.querySelectorAll("style")) while (n < r.length) if (r[n++].id === e) return !0 }, t.importCssString = function (n, r, i) { var s = i; if (!i || !i.getRootNode) s = document; else { s = i.getRootNode(); if (!s || s == i) s = document } var o = s.ownerDocument || s; if (r && t.hasCssString(r, s)) return null; r && (n += "\n/*# sourceURL=ace/css/" + r + " */"); var u = t.createElement("style"); u.appendChild(o.createTextNode(n)), r && (u.id = r), s == o && (s = t.getDocumentHead(o)), s.insertBefore(u, s.firstChild) }, t.importCssStylsheet = function (e, n) { t.buildDom(["link", { rel: "stylesheet", href: e }], t.getDocumentHead(n)) }, t.scrollbarWidth = function (e) { var n = t.createElement("ace_inner"); n.style.width = "100%", n.style.minWidth = "0px", n.style.height = "200px", n.style.display = "block"; var r = t.createElement("ace_outer"), i = r.style; i.position = "absolute", i.left = "-10000px", i.overflow = "hidden", i.width = "200px", i.minWidth = "0px", i.height = "150px", i.display = "block", r.appendChild(n); var s = e.documentElement; s.appendChild(r); var o = n.offsetWidth; i.overflow = "scroll"; var u = n.offsetWidth; return o == u && (u = r.clientWidth), s.removeChild(r), o - u }, typeof document == "undefined" && (t.importCssString = function () { }), t.computedStyle = function (e, t) { return window.getComputedStyle(e, "") || {} }, t.setStyle = function (e, t, n) { e[t] !== n && (e[t] = n) }, t.HAS_CSS_ANIMATION = !1, t.HAS_CSS_TRANSFORMS = !1, t.HI_DPI = r.isWin ? typeof window != "undefined" && window.devicePixelRatio >= 1.5 : !0; if (typeof document != "undefined") { var s = document.createElement("div"); t.HI_DPI && s.style.transform !== undefined && (t.HAS_CSS_TRANSFORMS = !0), !r.isEdge && typeof s.style.animationName != "undefined" && (t.HAS_CSS_ANIMATION = !0), s = null } t.HAS_CSS_TRANSFORMS ? t.translate = function (e, t, n) { e.style.transform = "translate(" + Math.round(t) + "px, " + Math.round(n) + "px)" } : t.translate = function (e, t, n) { e.style.top = Math.round(n) + "px", e.style.left = Math.round(t) + "px" } }), define("ace/lib/oop", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.inherits = function (e, t) { e.super_ = t, e.prototype = Object.create(t.prototype, { constructor: { value: e, enumerable: !1, writable: !0, configurable: !0 } }) }, t.mixin = function (e, t) { for (var n in t) e[n] = t[n]; return e }, t.implement = function (e, n) { t.mixin(e, n) } }), define("ace/lib/keys", ["require", "exports", "module", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./oop"), i = function () { var e = { MODIFIER_KEYS: { 16: "Shift", 17: "Ctrl", 18: "Alt", 224: "Meta", 91: "MetaLeft", 92: "MetaRight", 93: "ContextMenu" }, KEY_MODS: { ctrl: 1, alt: 2, option: 2, shift: 4, "super": 8, meta: 8, command: 8, cmd: 8, control: 1 }, FUNCTION_KEYS: { 8: "Backspace", 9: "Tab", 13: "Return", 19: "Pause", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "Print", 45: "Insert", 46: "Delete", 96: "Numpad0", 97: "Numpad1", 98: "Numpad2", 99: "Numpad3", 100: "Numpad4", 101: "Numpad5", 102: "Numpad6", 103: "Numpad7", 104: "Numpad8", 105: "Numpad9", "-13": "NumpadEnter", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock", 145: "Scrolllock" }, PRINTABLE_KEYS: { 32: " ", 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9", 59: ";", 61: "=", 65: "a", 66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j", 75: "k", 76: "l", 77: "m", 78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t", 85: "u", 86: "v", 87: "w", 88: "x", 89: "y", 90: "z", 107: "+", 109: "-", 110: ".", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 111: "/", 106: "*" } }, t, n; for (n in e.FUNCTION_KEYS) t = e.FUNCTION_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); for (n in e.PRINTABLE_KEYS) t = e.PRINTABLE_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); return r.mixin(e, e.MODIFIER_KEYS), r.mixin(e, e.PRINTABLE_KEYS), r.mixin(e, e.FUNCTION_KEYS), e.enter = e["return"], e.escape = e.esc, e.del = e["delete"], e[173] = "-", function () { var t = ["cmd", "ctrl", "alt", "shift"]; for (var n = Math.pow(2, t.length); n--;)e.KEY_MODS[n] = t.filter(function (t) { return n & e.KEY_MODS[t] }).join("-") + "-" }(), e.KEY_MODS[0] = "", e.KEY_MODS[-1] = "input-", e }(); r.mixin(t, i), t.keyCodeToString = function (e) { var t = i[e]; return typeof t != "string" && (t = String.fromCharCode(e)), t.toLowerCase() } }), define("ace/lib/event", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function a() { u = !1; try { document.createComment("").addEventListener("test", function () { }, { get passive() { u = { passive: !1 } } }) } catch (e) { } } function f() { return u == undefined && a(), u } function l(e, t, n) { this.elem = e, this.type = t, this.callback = n } function d(e, t, n) { var u = p(t); if (!i.isMac && s) { t.getModifierState && (t.getModifierState("OS") || t.getModifierState("Win")) && (u |= 8); if (s.altGr) { if ((3 & u) == 3) return; s.altGr = 0 } if (n === 18 || n === 17) { var a = "location" in t ? t.location : t.keyLocation; if (n === 17 && a === 1) s[n] == 1 && (o = t.timeStamp); else if (n === 18 && u === 3 && a === 2) { var f = t.timeStamp - o; f < 50 && (s.altGr = !0) } } } n in r.MODIFIER_KEYS && (n = -1); if (!u && n === 13) { var a = "location" in t ? t.location : t.keyLocation; if (a === 3) { e(t, u, -n); if (t.defaultPrevented) return } } if (i.isChromeOS && u & 8) { e(t, u, n); if (t.defaultPrevented) return; u &= -9 } return !!u || n in r.FUNCTION_KEYS || n in r.PRINTABLE_KEYS ? e(t, u, n) : !1 } function v() { s = Object.create(null) } var r = e("./keys"), i = e("./useragent"), s = null, o = 0, u; l.prototype.destroy = function () { h(this.elem, this.type, this.callback), this.elem = this.type = this.callback = undefined }; var c = t.addListener = function (e, t, n, r) { e.addEventListener(t, n, f()), r && r.$toDestroy.push(new l(e, t, n)) }, h = t.removeListener = function (e, t, n) { e.removeEventListener(t, n, f()) }; t.stopEvent = function (e) { return t.stopPropagation(e), t.preventDefault(e), !1 }, t.stopPropagation = function (e) { e.stopPropagation && e.stopPropagation() }, t.preventDefault = function (e) { e.preventDefault && e.preventDefault() }, t.getButton = function (e) { return e.type == "dblclick" ? 0 : e.type == "contextmenu" || i.isMac && e.ctrlKey && !e.altKey && !e.shiftKey ? 2 : e.button }, t.capture = function (e, t, n) { function r(e) { t && t(e), n && n(e), h(document, "mousemove", t), h(document, "mouseup", r), h(document, "dragstart", r) } return c(document, "mousemove", t), c(document, "mouseup", r), c(document, "dragstart", r), r }, t.addMouseWheelListener = function (e, t, n) { "onmousewheel" in e ? c(e, "mousewheel", function (e) { var n = 8; e.wheelDeltaX !== undefined ? (e.wheelX = -e.wheelDeltaX / n, e.wheelY = -e.wheelDeltaY / n) : (e.wheelX = 0, e.wheelY = -e.wheelDelta / n), t(e) }, n) : "onwheel" in e ? c(e, "wheel", function (e) { var n = .35; switch (e.deltaMode) { case e.DOM_DELTA_PIXEL: e.wheelX = e.deltaX * n || 0, e.wheelY = e.deltaY * n || 0; break; case e.DOM_DELTA_LINE: case e.DOM_DELTA_PAGE: e.wheelX = (e.deltaX || 0) * 5, e.wheelY = (e.deltaY || 0) * 5 }t(e) }, n) : c(e, "DOMMouseScroll", function (e) { e.axis && e.axis == e.HORIZONTAL_AXIS ? (e.wheelX = (e.detail || 0) * 5, e.wheelY = 0) : (e.wheelX = 0, e.wheelY = (e.detail || 0) * 5), t(e) }, n) }, t.addMultiMouseDownListener = function (e, n, r, s, o) { function p(e) { t.getButton(e) !== 0 ? u = 0 : e.detail > 1 ? (u++, u > 4 && (u = 1)) : u = 1; if (i.isIE) { var o = Math.abs(e.clientX - a) > 5 || Math.abs(e.clientY - f) > 5; if (!l || o) u = 1; l && clearTimeout(l), l = setTimeout(function () { l = null }, n[u - 1] || 600), u == 1 && (a = e.clientX, f = e.clientY) } e._clicks = u, r[s]("mousedown", e); if (u > 4) u = 0; else if (u > 1) return r[s](h[u], e) } var u = 0, a, f, l, h = { 2: "dblclick", 3: "tripleclick", 4: "quadclick" }; Array.isArray(e) || (e = [e]), e.forEach(function (e) { c(e, "mousedown", p, o) }) }; var p = function (e) { return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0) }; t.getModifierString = function (e) { return r.KEY_MODS[p(e)] }, t.addCommandKeyListener = function (e, n, r) { if (i.isOldGecko || i.isOpera && !("KeyboardEvent" in window)) { var o = null; c(e, "keydown", function (e) { o = e.keyCode }, r), c(e, "keypress", function (e) { return d(n, e, o) }, r) } else { var u = null; c(e, "keydown", function (e) { s[e.keyCode] = (s[e.keyCode] || 0) + 1; var t = d(n, e, e.keyCode); return u = e.defaultPrevented, t }, r), c(e, "keypress", function (e) { u && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && (t.stopEvent(e), u = null) }, r), c(e, "keyup", function (e) { s[e.keyCode] = null }, r), s || (v(), c(window, "focus", v)) } }; if (typeof window == "object" && window.postMessage && !i.isOldIE) { var m = 1; t.nextTick = function (e, n) { n = n || window; var r = "zero-timeout-message-" + m++, i = function (s) { s.data == r && (t.stopPropagation(s), h(n, "message", i), e()) }; c(n, "message", i), n.postMessage(r, "*") } } t.$idleBlocked = !1, t.onIdle = function (e, n) { return setTimeout(function r() { t.$idleBlocked ? setTimeout(r, 100) : e() }, n) }, t.$idleBlockId = null, t.blockIdle = function (e) { t.$idleBlockId && clearTimeout(t.$idleBlockId), t.$idleBlocked = !0, t.$idleBlockId = setTimeout(function () { t.$idleBlocked = !1 }, e || 100) }, t.nextFrame = typeof window == "object" && (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame), t.nextFrame ? t.nextFrame = t.nextFrame.bind(window) : t.nextFrame = function (e) { setTimeout(e, 17) } }), define("ace/range", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function (e, t) { return e.row - t.row || e.column - t.column }, i = function (e, t, n, r) { this.start = { row: e, column: t }, this.end = { row: n, column: r } }; (function () { this.isEqual = function (e) { return this.start.row === e.start.row && this.end.row === e.end.row && this.start.column === e.start.column && this.end.column === e.end.column }, this.toString = function () { return "Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]" }, this.contains = function (e, t) { return this.compare(e, t) == 0 }, this.compareRange = function (e) { var t, n = e.end, r = e.start; return t = this.compare(n.row, n.column), t == 1 ? (t = this.compare(r.row, r.column), t == 1 ? 2 : t == 0 ? 1 : 0) : t == -1 ? -2 : (t = this.compare(r.row, r.column), t == -1 ? -1 : t == 1 ? 42 : 0) }, this.comparePoint = function (e) { return this.compare(e.row, e.column) }, this.containsRange = function (e) { return this.comparePoint(e.start) == 0 && this.comparePoint(e.end) == 0 }, this.intersects = function (e) { var t = this.compareRange(e); return t == -1 || t == 0 || t == 1 }, this.isEnd = function (e, t) { return this.end.row == e && this.end.column == t }, this.isStart = function (e, t) { return this.start.row == e && this.start.column == t }, this.setStart = function (e, t) { typeof e == "object" ? (this.start.column = e.column, this.start.row = e.row) : (this.start.row = e, this.start.column = t) }, this.setEnd = function (e, t) { typeof e == "object" ? (this.end.column = e.column, this.end.row = e.row) : (this.end.row = e, this.end.column = t) }, this.inside = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) || this.isStart(e, t) ? !1 : !0 : !1 }, this.insideStart = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) ? !1 : !0 : !1 }, this.insideEnd = function (e, t) { return this.compare(e, t) == 0 ? this.isStart(e, t) ? !1 : !0 : !1 }, this.compare = function (e, t) { return !this.isMultiLine() && e === this.start.row ? t < this.start.column ? -1 : t > this.end.column ? 1 : 0 : e < this.start.row ? -1 : e > this.end.row ? 1 : this.start.row === e ? t >= this.start.column ? 0 : -1 : this.end.row === e ? t <= this.end.column ? 0 : 1 : 0 }, this.compareStart = function (e, t) { return this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.compareEnd = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.compare(e, t) }, this.compareInside = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.clipRows = function (e, t) { if (this.end.row > t) var n = { row: t + 1, column: 0 }; else if (this.end.row < e) var n = { row: e, column: 0 }; if (this.start.row > t) var r = { row: t + 1, column: 0 }; else if (this.start.row < e) var r = { row: e, column: 0 }; return i.fromPoints(r || this.start, n || this.end) }, this.extend = function (e, t) { var n = this.compare(e, t); if (n == 0) return this; if (n == -1) var r = { row: e, column: t }; else var s = { row: e, column: t }; return i.fromPoints(r || this.start, s || this.end) }, this.isEmpty = function () { return this.start.row === this.end.row && this.start.column === this.end.column }, this.isMultiLine = function () { return this.start.row !== this.end.row }, this.clone = function () { return i.fromPoints(this.start, this.end) }, this.collapseRows = function () { return this.end.column == 0 ? new i(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0) : new i(this.start.row, 0, this.end.row, 0) }, this.toScreenRange = function (e) { var t = e.documentToScreenPosition(this.start), n = e.documentToScreenPosition(this.end); return new i(t.row, t.column, n.row, n.column) }, this.moveBy = function (e, t) { this.start.row += e, this.start.column += t, this.end.row += e, this.end.column += t } }).call(i.prototype), i.fromPoints = function (e, t) { return new i(e.row, e.column, t.row, t.column) }, i.comparePoints = r, i.comparePoints = function (e, t) { return e.row - t.row || e.column - t.column }, t.Range = i }), define("ace/lib/lang", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.last = function (e) { return e[e.length - 1] }, t.stringReverse = function (e) { return e.split("").reverse().join("") }, t.stringRepeat = function (e, t) { var n = ""; while (t > 0) { t & 1 && (n += e); if (t >>= 1) e += e } return n }; var r = /^\s\s*/, i = /\s\s*$/; t.stringTrimLeft = function (e) { return e.replace(r, "") }, t.stringTrimRight = function (e) { return e.replace(i, "") }, t.copyObject = function (e) { var t = {}; for (var n in e) t[n] = e[n]; return t }, t.copyArray = function (e) { var t = []; for (var n = 0, r = e.length; n < r; n++)e[n] && typeof e[n] == "object" ? t[n] = this.copyObject(e[n]) : t[n] = e[n]; return t }, t.deepCopy = function s(e) { if (typeof e != "object" || !e) return e; var t; if (Array.isArray(e)) { t = []; for (var n = 0; n < e.length; n++)t[n] = s(e[n]); return t } if (Object.prototype.toString.call(e) !== "[object Object]") return e; t = {}; for (var n in e) t[n] = s(e[n]); return t }, t.arrayToMap = function (e) { var t = {}; for (var n = 0; n < e.length; n++)t[e[n]] = 1; return t }, t.createMap = function (e) { var t = Object.create(null); for (var n in e) t[n] = e[n]; return t }, t.arrayRemove = function (e, t) { for (var n = 0; n <= e.length; n++)t === e[n] && e.splice(n, 1) }, t.escapeRegExp = function (e) { return e.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1") }, t.escapeHTML = function (e) { return ("" + e).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/ Date.now() - 50 ? !0 : r = !1 }, cancel: function () { r = Date.now() } } }), define("ace/keyboard/textinput", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/lib/dom", "ace/lib/lang", "ace/clipboard", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("../lib/dom"), o = e("../lib/lang"), u = e("../clipboard"), a = i.isChrome < 18, f = i.isIE, l = i.isChrome > 63, c = 400, h = e("../lib/keys"), p = h.KEY_MODS, d = i.isIOS, v = d ? /\s/ : /\n/, m = i.isMobile, g = function (e, t) { function X() { x = !0, n.blur(), n.focus(), x = !1 } function $(e) { e.keyCode == 27 && n.value.length < n.selectionStart && (b || (T = n.value), N = C = -1, O()), V() } function K() { clearTimeout(J), J = setTimeout(function () { E && (n.style.cssText = E, E = ""), t.renderer.$isMousePressed = !1, t.renderer.$keepTextAreaAtCursor && t.renderer.$moveTextAreaToCursor() }, 0) } function G(e, t, n) { var r = null, i = !1; n.addEventListener("keydown", function (e) { r && clearTimeout(r), i = !0 }, !0), n.addEventListener("keyup", function (e) { r = setTimeout(function () { i = !1 }, 100) }, !0); var s = function (e) { if (document.activeElement !== n) return; if (i || b || t.$mouseHandler.isMousePressed) return; if (g) return; var r = n.selectionStart, s = n.selectionEnd, o = null, u = 0; if (r == 0) o = h.up; else if (r == 1) o = h.home; else if (s > C && T[s] == "\n") o = h.end; else if (r < N && T[r - 1] == " ") o = h.left, u = p.option; else if (r < N || r == N && C != N && r == s) o = h.left; else if (s > C && T.slice(0, s).split("\n").length > 2) o = h.down; else if (s > C && T[s - 1] == " ") o = h.right, u = p.option; else if (s > C || s == C && C != N && r == s) o = h.right; r !== s && (u |= p.shift); if (o) { var a = t.onCommandKey({}, u, o); if (!a && t.commands) { o = h.keyCodeToString(o); var f = t.commands.findKeyCommand(u, o); f && t.execCommand(f) } N = r, C = s, O("") } }; document.addEventListener("selectionchange", s), t.on("destroy", function () { document.removeEventListener("selectionchange", s) }) } var n = s.createElement("textarea"); n.className = "ace_text-input", n.setAttribute("wrap", "off"), n.setAttribute("autocorrect", "off"), n.setAttribute("autocapitalize", "off"), n.setAttribute("spellcheck", !1), n.style.opacity = "0", e.insertBefore(n, e.firstChild); var g = !1, y = !1, b = !1, w = !1, E = ""; m || (n.style.fontSize = "1px"); var S = !1, x = !1, T = "", N = 0, C = 0, k = 0; try { var L = document.activeElement === n } catch (A) { } r.addListener(n, "blur", function (e) { if (x) return; t.onBlur(e), L = !1 }, t), r.addListener(n, "focus", function (e) { if (x) return; L = !0; if (i.isEdge) try { if (!document.hasFocus()) return } catch (e) { } t.onFocus(e), i.isEdge ? setTimeout(O) : O() }, t), this.$focusScroll = !1, this.focus = function () { if (E || l || this.$focusScroll == "browser") return n.focus({ preventScroll: !0 }); var e = n.style.top; n.style.position = "fixed", n.style.top = "0px"; try { var t = n.getBoundingClientRect().top != 0 } catch (r) { return } var i = []; if (t) { var s = n.parentElement; while (s && s.nodeType == 1) i.push(s), s.setAttribute("ace_nocontext", !0), !s.parentElement && s.getRootNode ? s = s.getRootNode().host : s = s.parentElement } n.focus({ preventScroll: !0 }), t && i.forEach(function (e) { e.removeAttribute("ace_nocontext") }), setTimeout(function () { n.style.position = "", n.style.top == "0px" && (n.style.top = e) }, 0) }, this.blur = function () { n.blur() }, this.isFocused = function () { return L }, t.on("beforeEndOperation", function () { var e = t.curOp, r = e && e.command && e.command.name; if (r == "insertstring") return; var i = r && (e.docChanged || e.selectionChanged); b && i && (T = n.value = "", W()), O() }); var O = d ? function (e) { if (!L || g && !e || w) return; e || (e = ""); var r = "\n ab" + e + "cde fg\n"; r != n.value && (n.value = T = r); var i = 4, s = 4 + (e.length || (t.selection.isEmpty() ? 0 : 1)); (N != i || C != s) && n.setSelectionRange(i, s), N = i, C = s } : function () { if (b || w) return; if (!L && !P) return; b = !0; var e = 0, r = 0, i = ""; if (t.session) { var s = t.selection, o = s.getRange(), u = s.cursor.row; e = o.start.column, r = o.end.column, i = t.session.getLine(u); if (o.start.row != u) { var a = t.session.getLine(u - 1); e = o.start.row < u - 1 ? 0 : e, r += a.length + 1, i = a + "\n" + i } else if (o.end.row != u) { var f = t.session.getLine(u + 1); r = o.end.row > u + 1 ? f.length : r, r += i.length + 1, i = i + "\n" + f } else m && u > 0 && (i = "\n" + i, r += 1, e += 1); i.length > c && (e < c && r < c ? i = i.slice(0, c) : (i = "\n", e = 0, r = 1)) } var l = i + "\n\n"; l != T && (n.value = T = l, N = C = l.length), P && (N = n.selectionStart, C = n.selectionEnd); if (C != r || N != e || n.selectionEnd != C) try { n.setSelectionRange(e, r), N = e, C = r } catch (h) { } b = !1 }; this.resetSelection = O, L && t.onFocus(); var M = function (e) { return e.selectionStart === 0 && e.selectionEnd >= T.length && e.value === T && T && e.selectionEnd !== C }, _ = function (e) { if (b) return; g ? g = !1 : M(n) ? (t.selectAll(), O()) : m && n.selectionStart != N && O() }, D = null; this.setInputHandler = function (e) { D = e }, this.getInputHandler = function () { return D }; var P = !1, H = function (e, r) { P && (P = !1); if (y) return O(), e && t.onPaste(e), y = !1, ""; var i = n.selectionStart, s = n.selectionEnd, o = N, u = T.length - C, a = e, f = e.length - i, l = e.length - s, c = 0; while (o > 0 && T[c] == e[c]) c++, o--; a = a.slice(c), c = 1; while (u > 0 && T.length - c > N - 1 && T[T.length - c] == e[e.length - c]) c++, u--; f -= c - 1, l -= c - 1; var h = a.length - c + 1; return h < 0 && (o = -h, h = 0), a = a.slice(0, h), !r && !a && !f && !o && !u && !l ? "" : (w = !0, a && !o && !u && !f && !l || S ? t.onTextInput(a) : t.onTextInput(a, { extendLeft: o, extendRight: u, restoreStart: f, restoreEnd: l }), w = !1, T = e, N = i, C = s, k = l, a) }, B = function (e) { if (b) return z(); if (e && e.inputType) { if (e.inputType == "historyUndo") return t.execCommand("undo"); if (e.inputType == "historyRedo") return t.execCommand("redo") } var r = n.value, i = H(r, !0); (r.length > c + 100 || v.test(i) || m && N < 1 && N == C) && O() }, j = function (e, t, n) { var r = e.clipboardData || window.clipboardData; if (!r || a) return; var i = f || n ? "Text" : "text/plain"; try { return t ? r.setData(i, t) !== !1 : r.getData(i) } catch (e) { if (!n) return j(e, t, !0) } }, F = function (e, i) { var s = t.getCopyText(); if (!s) return r.preventDefault(e); j(e, s) ? (d && (O(s), g = s, setTimeout(function () { g = !1 }, 10)), i ? t.onCut() : t.onCopy(), r.preventDefault(e)) : (g = !0, n.value = s, n.select(), setTimeout(function () { g = !1, O(), i ? t.onCut() : t.onCopy() })) }, I = function (e) { F(e, !0) }, q = function (e) { F(e, !1) }, R = function (e) { var s = j(e); if (u.pasteCancelled()) return; typeof s == "string" ? (s && t.onPaste(s, e), i.isIE && setTimeout(O), r.preventDefault(e)) : (n.value = "", y = !0) }; r.addCommandKeyListener(n, t.onCommandKey.bind(t), t), r.addListener(n, "select", _, t), r.addListener(n, "input", B, t), r.addListener(n, "cut", I, t), r.addListener(n, "copy", q, t), r.addListener(n, "paste", R, t), (!("oncut" in n) || !("oncopy" in n) || !("onpaste" in n)) && r.addListener(e, "keydown", function (e) { if (i.isMac && !e.metaKey || !e.ctrlKey) return; switch (e.keyCode) { case 67: q(e); break; case 86: R(e); break; case 88: I(e) } }, t); var U = function (e) { if (b || !t.onCompositionStart || t.$readOnly) return; b = {}; if (S) return; e.data && (b.useTextareaForIME = !1), setTimeout(z, 0), t._signal("compositionStart"), t.on("mousedown", X); var r = t.getSelectionRange(); r.end.row = r.start.row, r.end.column = r.start.column, b.markerRange = r, b.selectionStart = N, t.onCompositionStart(b), b.useTextareaForIME ? (T = n.value = "", N = 0, C = 0) : (n.msGetInputContext && (b.context = n.msGetInputContext()), n.getInputContext && (b.context = n.getInputContext())) }, z = function () { if (!b || !t.onCompositionUpdate || t.$readOnly) return; if (S) return X(); if (b.useTextareaForIME) t.onCompositionUpdate(n.value); else { var e = n.value; H(e), b.markerRange && (b.context && (b.markerRange.start.column = b.selectionStart = b.context.compositionStartOffset), b.markerRange.end.column = b.markerRange.start.column + C - b.selectionStart + k) } }, W = function (e) { if (!t.onCompositionEnd || t.$readOnly) return; b = !1, t.onCompositionEnd(), t.off("mousedown", X), e && B() }, V = o.delayedCall(z, 50).schedule.bind(null, null); r.addListener(n, "compositionstart", U, t), r.addListener(n, "compositionupdate", z, t), r.addListener(n, "keyup", $, t), r.addListener(n, "keydown", V, t), r.addListener(n, "compositionend", W, t), this.getElement = function () { return n }, this.setCommandMode = function (e) { S = e, n.readOnly = !1 }, this.setReadOnly = function (e) { S || (n.readOnly = e) }, this.setCopyWithEmptySelection = function (e) { }, this.onContextMenu = function (e) { P = !0, O(), t._emit("nativecontextmenu", { target: t, domEvent: e }), this.moveToMouse(e, !0) }, this.moveToMouse = function (e, o) { E || (E = n.style.cssText), n.style.cssText = (o ? "z-index:100000;" : "") + (i.isIE ? "opacity:0.1;" : "") + "text-indent: -" + (N + C) * t.renderer.characterWidth * .5 + "px;"; var u = t.container.getBoundingClientRect(), a = s.computedStyle(t.container), f = u.top + (parseInt(a.borderTopWidth) || 0), l = u.left + (parseInt(u.borderLeftWidth) || 0), c = u.bottom - f - n.clientHeight - 2, h = function (e) { s.translate(n, e.clientX - l - 2, Math.min(e.clientY - f - 2, c)) }; h(e); if (e.type != "mousedown") return; t.renderer.$isMousePressed = !0, clearTimeout(J), i.isWin && r.capture(t.container, h, K) }, this.onContextMenuClose = K; var J, Q = function (e) { t.textInput.onContextMenu(e), K() }; r.addListener(n, "mouseup", Q, t), r.addListener(n, "mousedown", function (e) { e.preventDefault(), K() }, t), r.addListener(t.renderer.scroller, "contextmenu", Q, t), r.addListener(n, "contextmenu", Q, t), d && G(e, t, n) }; t.TextInput = g, t.$setUserAgentForTests = function (e, t) { m = e, d = t } }), define("ace/mouse/default_handlers", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e) { e.$clickSelection = null; var t = e.editor; t.setDefaultHandler("mousedown", this.onMouseDown.bind(e)), t.setDefaultHandler("dblclick", this.onDoubleClick.bind(e)), t.setDefaultHandler("tripleclick", this.onTripleClick.bind(e)), t.setDefaultHandler("quadclick", this.onQuadClick.bind(e)), t.setDefaultHandler("mousewheel", this.onMouseWheel.bind(e)); var n = ["select", "startSelect", "selectEnd", "selectAllEnd", "selectByWordsEnd", "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; n.forEach(function (t) { e[t] = this[t] }, this), e.selectByLines = this.extendSelectionBy.bind(e, "getLineRange"), e.selectByWords = this.extendSelectionBy.bind(e, "getWordRange") } function u(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } function a(e, t) { if (e.start.row == e.end.row) var n = 2 * t.column - e.start.column - e.end.column; else if (e.start.row == e.end.row - 1 && !e.start.column && !e.end.column) var n = t.column - 4; else var n = 2 * t.row - e.start.row - e.end.row; return n < 0 ? { cursor: e.start, anchor: e.end } : { cursor: e.end, anchor: e.start } } var r = e("../lib/useragent"), i = 0, s = 550; (function () { this.onMouseDown = function (e) { var t = e.inSelection(), n = e.getDocumentPosition(); this.mousedownEvent = e; var i = this.editor, s = e.getButton(); if (s !== 0) { var o = i.getSelectionRange(), u = o.isEmpty(); (u || s == 1) && i.selection.moveToPosition(n), s == 2 && (i.textInput.onContextMenu(e.domEvent), r.isMozilla || e.preventDefault()); return } this.mousedownEvent.time = Date.now(); if (t && !i.isFocused()) { i.focus(); if (this.$focusTimeout && !this.$clickSelection && !i.inMultiSelectMode) { this.setState("focusWait"), this.captureMouse(e); return } } return this.captureMouse(e), this.startSelect(n, e.domEvent._clicks > 1), e.preventDefault() }, this.startSelect = function (e, t) { e = e || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var n = this.editor; if (!this.mousedownEvent) return; this.mousedownEvent.getShiftKey() ? n.selection.selectToPosition(e) : t || n.selection.moveToPosition(e), t || this.select(), n.renderer.scroller.setCapture && n.renderer.scroller.setCapture(), n.setStyle("ace_selecting"), this.setState("select") }, this.select = function () { var e, t = this.editor, n = t.renderer.screenToTextCoordinates(this.x, this.y); if (this.$clickSelection) { var r = this.$clickSelection.comparePoint(n); if (r == -1) e = this.$clickSelection.end; else if (r == 1) e = this.$clickSelection.start; else { var i = a(this.$clickSelection, n); n = i.cursor, e = i.anchor } t.selection.setSelectionAnchor(e.row, e.column) } t.selection.selectToPosition(n), t.renderer.scrollCursorIntoView() }, this.extendSelectionBy = function (e) { var t, n = this.editor, r = n.renderer.screenToTextCoordinates(this.x, this.y), i = n.selection[e](r.row, r.column); if (this.$clickSelection) { var s = this.$clickSelection.comparePoint(i.start), o = this.$clickSelection.comparePoint(i.end); if (s == -1 && o <= 0) { t = this.$clickSelection.end; if (i.end.row != r.row || i.end.column != r.column) r = i.start } else if (o == 1 && s >= 0) { t = this.$clickSelection.start; if (i.start.row != r.row || i.start.column != r.column) r = i.end } else if (s == -1 && o == 1) r = i.end, t = i.start; else { var u = a(this.$clickSelection, r); r = u.cursor, t = u.anchor } n.selection.setSelectionAnchor(t.row, t.column) } n.selection.selectToPosition(r), n.renderer.scrollCursorIntoView() }, this.selectEnd = this.selectAllEnd = this.selectByWordsEnd = this.selectByLinesEnd = function () { this.$clickSelection = null, this.editor.unsetStyle("ace_selecting"), this.editor.renderer.scroller.releaseCapture && this.editor.renderer.scroller.releaseCapture() }, this.focusWait = function () { var e = u(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y), t = Date.now(); (e > i || t - this.mousedownEvent.time > this.$focusTimeout) && this.startSelect(this.mousedownEvent.getDocumentPosition()) }, this.onDoubleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor, r = n.session, i = r.getBracketRange(t); i ? (i.isEmpty() && (i.start.column--, i.end.column++), this.setState("select")) : (i = n.selection.getWordRange(t.row, t.column), this.setState("selectByWords")), this.$clickSelection = i, this.select() }, this.onTripleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor; this.setState("selectByLines"); var r = n.getSelectionRange(); r.isMultiLine() && r.contains(t.row, t.column) ? (this.$clickSelection = n.selection.getLineRange(r.start.row), this.$clickSelection.end = n.selection.getLineRange(r.end.row).end) : this.$clickSelection = n.selection.getLineRange(t.row), this.select() }, this.onQuadClick = function (e) { var t = this.editor; t.selectAll(), this.$clickSelection = t.getSelectionRange(), this.setState("selectAll") }, this.onMouseWheel = function (e) { if (e.getAccelKey()) return; e.getShiftKey() && e.wheelY && !e.wheelX && (e.wheelX = e.wheelY, e.wheelY = 0); var t = this.editor; this.$lastScroll || (this.$lastScroll = { t: 0, vx: 0, vy: 0, allowed: 0 }); var n = this.$lastScroll, r = e.domEvent.timeStamp, i = r - n.t, o = i ? e.wheelX / i : n.vx, u = i ? e.wheelY / i : n.vy; i < s && (o = (o + n.vx) / 2, u = (u + n.vy) / 2); var a = Math.abs(o / u), f = !1; a >= 1 && t.renderer.isScrollableBy(e.wheelX * e.speed, 0) && (f = !0), a <= 1 && t.renderer.isScrollableBy(0, e.wheelY * e.speed) && (f = !0); if (f) n.allowed = r; else if (r - n.allowed < s) { var l = Math.abs(o) <= 1.5 * Math.abs(n.vx) && Math.abs(u) <= 1.5 * Math.abs(n.vy); l ? (f = !0, n.allowed = r) : n.allowed = 0 } n.t = r, n.vx = o, n.vy = u; if (f) return t.renderer.scrollBy(e.wheelX * e.speed, e.wheelY * e.speed), e.stop() } }).call(o.prototype), t.DefaultHandlers = o }), define("ace/tooltip", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom"], function (e, t, n) { "use strict"; function s(e) { this.isOpen = !1, this.$element = null, this.$parentNode = e } var r = e("./lib/oop"), i = e("./lib/dom"); (function () { this.$init = function () { return this.$element = i.createElement("div"), this.$element.className = "ace_tooltip", this.$element.style.display = "none", this.$parentNode.appendChild(this.$element), this.$element }, this.getElement = function () { return this.$element || this.$init() }, this.setText = function (e) { this.getElement().textContent = e }, this.setHtml = function (e) { this.getElement().innerHTML = e }, this.setPosition = function (e, t) { this.getElement().style.left = e + "px", this.getElement().style.top = t + "px" }, this.setClassName = function (e) { i.addCssClass(this.getElement(), e) }, this.show = function (e, t, n) { e != null && this.setText(e), t != null && n != null && this.setPosition(t, n), this.isOpen || (this.getElement().style.display = "block", this.isOpen = !0) }, this.hide = function () { this.isOpen && (this.getElement().style.display = "none", this.isOpen = !1) }, this.getHeight = function () { return this.getElement().offsetHeight }, this.getWidth = function () { return this.getElement().offsetWidth }, this.destroy = function () { this.isOpen = !1, this.$element && this.$element.parentNode && this.$element.parentNode.removeChild(this.$element) } }).call(s.prototype), t.Tooltip = s }), define("ace/mouse/default_gutter_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/event", "ace/tooltip"], function (e, t, n) { "use strict"; function u(e) { function l() { var r = u.getDocumentPosition().row, s = n.$annotations[r]; if (!s) return c(); var o = t.session.getLength(); if (r == o) { var a = t.renderer.pixelToScreenCoordinates(0, u.y).row, l = u.$pos; if (a > t.session.documentToScreenRow(l.row, l.column)) return c() } if (f == s) return; f = s.text.join("
"), i.setHtml(f), i.show(), t._signal("showGutterTooltip", i), t.on("mousewheel", c); if (e.$tooltipFollowsMouse) h(u); else { var p = u.domEvent.target, d = p.getBoundingClientRect(), v = i.getElement().style; v.left = d.right + "px", v.top = d.bottom + "px" } } function c() { o && (o = clearTimeout(o)), f && (i.hide(), f = null, t._signal("hideGutterTooltip", i), t.off("mousewheel", c)) } function h(e) { i.setPosition(e.x, e.y) } var t = e.editor, n = t.renderer.$gutterLayer, i = new a(t.container); e.editor.setDefaultHandler("guttermousedown", function (r) { if (!t.isFocused() || r.getButton() != 0) return; var i = n.getRegion(r); if (i == "foldWidgets") return; var s = r.getDocumentPosition().row, o = t.session.selection; if (r.getShiftKey()) o.selectTo(s, 0); else { if (r.domEvent.detail == 2) return t.selectAll(), r.preventDefault(); e.$clickSelection = t.selection.getLineRange(s) } return e.setState("selectByLines"), e.captureMouse(r), r.preventDefault() }); var o, u, f; e.editor.setDefaultHandler("guttermousemove", function (t) { var n = t.domEvent.target || t.domEvent.srcElement; if (r.hasCssClass(n, "ace_fold-widget")) return c(); f && e.$tooltipFollowsMouse && h(t), u = t; if (o) return; o = setTimeout(function () { o = null, u && !e.isMousePressed ? l() : c() }, 50) }), s.addListener(t.renderer.$gutter, "mouseout", function (e) { u = null; if (!f || o) return; o = setTimeout(function () { o = null, c() }, 50) }, t), t.on("changeSession", c) } function a(e) { o.call(this, e) } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/event"), o = e("../tooltip").Tooltip; i.inherits(a, o), function () { this.setPosition = function (e, t) { var n = window.innerWidth || document.documentElement.clientWidth, r = window.innerHeight || document.documentElement.clientHeight, i = this.getWidth(), s = this.getHeight(); e += 15, t += 15, e + i > n && (e -= e + i - n), t + s > r && (t -= 20 + s), o.prototype.setPosition.call(this, e, t) } }.call(a.prototype), t.GutterHandler = u }), define("ace/mouse/mouse_event", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = t.MouseEvent = function (e, t) { this.domEvent = e, this.editor = t, this.x = this.clientX = e.clientX, this.y = this.clientY = e.clientY, this.$pos = null, this.$inSelection = null, this.propagationStopped = !1, this.defaultPrevented = !1 }; (function () { this.stopPropagation = function () { r.stopPropagation(this.domEvent), this.propagationStopped = !0 }, this.preventDefault = function () { r.preventDefault(this.domEvent), this.defaultPrevented = !0 }, this.stop = function () { this.stopPropagation(), this.preventDefault() }, this.getDocumentPosition = function () { return this.$pos ? this.$pos : (this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY), this.$pos) }, this.inSelection = function () { if (this.$inSelection !== null) return this.$inSelection; var e = this.editor, t = e.getSelectionRange(); if (t.isEmpty()) this.$inSelection = !1; else { var n = this.getDocumentPosition(); this.$inSelection = t.contains(n.row, n.column) } return this.$inSelection }, this.getButton = function () { return r.getButton(this.domEvent) }, this.getShiftKey = function () { return this.domEvent.shiftKey }, this.getAccelKey = i.isMac ? function () { return this.domEvent.metaKey } : function () { return this.domEvent.ctrlKey } }).call(s.prototype) }), define("ace/mouse/dragdrop_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; function f(e) { function T(e, n) { var r = Date.now(), i = !n || e.row != n.row, s = !n || e.column != n.column; if (!S || i || s) t.moveCursorToPosition(e), S = r, x = { x: p, y: d }; else { var o = l(x.x, x.y, p, d); o > a ? S = null : r - S >= u && (t.renderer.scrollCursorIntoView(), S = null) } } function N(e, n) { var r = Date.now(), i = t.renderer.layerConfig.lineHeight, s = t.renderer.layerConfig.characterWidth, u = t.renderer.scroller.getBoundingClientRect(), a = { x: { left: p - u.left, right: u.right - p }, y: { top: d - u.top, bottom: u.bottom - d } }, f = Math.min(a.x.left, a.x.right), l = Math.min(a.y.top, a.y.bottom), c = { row: e.row, column: e.column }; f / s <= 2 && (c.column += a.x.left < a.x.right ? -3 : 2), l / i <= 1 && (c.row += a.y.top < a.y.bottom ? -1 : 1); var h = e.row != c.row, v = e.column != c.column, m = !n || e.row != n.row; h || v && !m ? E ? r - E >= o && t.renderer.scrollCursorIntoView(c) : E = r : E = null } function C() { var e = g; g = t.renderer.screenToTextCoordinates(p, d), T(g, e), N(g, e) } function k() { m = t.selection.toOrientedRange(), h = t.session.addMarker(m, "ace_selection", t.getSelectionStyle()), t.clearSelection(), t.isFocused() && t.renderer.$cursorLayer.setBlinking(!1), clearInterval(v), C(), v = setInterval(C, 20), y = 0, i.addListener(document, "mousemove", O) } function L() { clearInterval(v), t.session.removeMarker(h), h = null, t.selection.fromOrientedRange(m), t.isFocused() && !w && t.$resetCursorStyle(), m = null, g = null, y = 0, E = null, S = null, i.removeListener(document, "mousemove", O) } function O() { A == null && (A = setTimeout(function () { A != null && h && L() }, 20)) } function M(e) { var t = e.types; return !t || Array.prototype.some.call(t, function (e) { return e == "text/plain" || e == "Text" }) } function _(e) { var t = ["copy", "copymove", "all", "uninitialized"], n = ["move", "copymove", "linkmove", "all", "uninitialized"], r = s.isMac ? e.altKey : e.ctrlKey, i = "uninitialized"; try { i = e.dataTransfer.effectAllowed.toLowerCase() } catch (e) { } var o = "none"; return r && t.indexOf(i) >= 0 ? o = "copy" : n.indexOf(i) >= 0 ? o = "move" : t.indexOf(i) >= 0 && (o = "copy"), o } var t = e.editor, n = r.createElement("img"); n.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", s.isOpera && (n.style.cssText = "width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;"); var f = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd", "onMouseDrag"]; f.forEach(function (t) { e[t] = this[t] }, this), t.on("mousedown", this.onMouseDown.bind(e)); var c = t.container, h, p, d, v, m, g, y = 0, b, w, E, S, x; this.onDragStart = function (e) { if (this.cancelDrag || !c.draggable) { var r = this; return setTimeout(function () { r.startSelect(), r.captureMouse(e) }, 0), e.preventDefault() } m = t.getSelectionRange(); var i = e.dataTransfer; i.effectAllowed = t.getReadOnly() ? "copy" : "copyMove", s.isOpera && (t.container.appendChild(n), n.scrollTop = 0), i.setDragImage && i.setDragImage(n, 0, 0), s.isOpera && t.container.removeChild(n), i.clearData(), i.setData("Text", t.session.getTextRange()), w = !0, this.setState("drag") }, this.onDragEnd = function (e) { c.draggable = !1, w = !1, this.setState(null); if (!t.getReadOnly()) { var n = e.dataTransfer.dropEffect; !b && n == "move" && t.session.remove(t.getSelectionRange()), t.$resetCursorStyle() } this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle("") }, this.onDragEnter = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || k(), y++, e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragOver = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || (k(), y++), A !== null && (A = null), e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragLeave = function (e) { y--; if (y <= 0 && h) return L(), b = null, i.preventDefault(e) }, this.onDrop = function (e) { if (!g) return; var n = e.dataTransfer; if (w) switch (b) { case "move": m.contains(g.row, g.column) ? m = { start: g, end: g } : m = t.moveText(m, g); break; case "copy": m = t.moveText(m, g, !0) } else { var r = n.getData("Text"); m = { start: g, end: t.session.insert(g, r) }, t.focus(), b = null } return L(), i.preventDefault(e) }, i.addListener(c, "dragstart", this.onDragStart.bind(e), t), i.addListener(c, "dragend", this.onDragEnd.bind(e), t), i.addListener(c, "dragenter", this.onDragEnter.bind(e), t), i.addListener(c, "dragover", this.onDragOver.bind(e), t), i.addListener(c, "dragleave", this.onDragLeave.bind(e), t), i.addListener(c, "drop", this.onDrop.bind(e), t); var A = null } function l(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } var r = e("../lib/dom"), i = e("../lib/event"), s = e("../lib/useragent"), o = 200, u = 200, a = 5; (function () { this.dragWait = function () { var e = Date.now() - this.mousedownEvent.time; e > this.editor.getDragDelay() && this.startDrag() }, this.dragWaitEnd = function () { var e = this.editor.container; e.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition()), this.selectEnd() }, this.dragReadyEnd = function (e) { this.editor.$resetCursorStyle(), this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle(""), this.dragWaitEnd() }, this.startDrag = function () { this.cancelDrag = !1; var e = this.editor, t = e.container; t.draggable = !0, e.renderer.$cursorLayer.setBlinking(!1), e.setStyle("ace_dragging"); var n = s.isWin ? "default" : "move"; e.renderer.setCursorStyle(n), this.setState("dragReady") }, this.onMouseDrag = function (e) { var t = this.editor.container; if (s.isIE && this.state == "dragReady") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 3 && t.dragDrop() } if (this.state === "dragWait") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 0 && (t.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition())) } }, this.onMouseDown = function (e) { if (!this.$dragEnabled) return; this.mousedownEvent = e; var t = this.editor, n = e.inSelection(), r = e.getButton(), i = e.domEvent.detail || 1; if (i === 1 && r === 0 && n) { if (e.editor.inMultiSelectMode && (e.getAccelKey() || e.getShiftKey())) return; this.mousedownEvent.time = Date.now(); var o = e.domEvent.target || e.domEvent.srcElement; "unselectable" in o && (o.unselectable = "on"); if (t.getDragDelay()) { if (s.isWebKit) { this.cancelDrag = !0; var u = t.container; u.draggable = !0 } this.setState("dragWait") } else this.startDrag(); this.captureMouse(e, this.onMouseDrag.bind(this)), e.defaultPrevented = !0 } } }).call(f.prototype), t.DragdropHandler = f }), define("ace/mouse/touch_handler", ["require", "exports", "module", "ace/mouse/mouse_event", "ace/lib/event", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./mouse_event").MouseEvent, i = e("../lib/event"), s = e("../lib/dom"); t.addTouchListeners = function (e, t) { function b() { var e = window.navigator && window.navigator.clipboard, r = !1, i = function () { var n = t.getCopyText(), i = t.session.getUndoManager().hasUndo(); y.replaceChild(s.buildDom(r ? ["span", !n && ["span", { "class": "ace_mobile-button", action: "selectall" }, "Select All"], n && ["span", { "class": "ace_mobile-button", action: "copy" }, "Copy"], n && ["span", { "class": "ace_mobile-button", action: "cut" }, "Cut"], e && ["span", { "class": "ace_mobile-button", action: "paste" }, "Paste"], i && ["span", { "class": "ace_mobile-button", action: "undo" }, "Undo"], ["span", { "class": "ace_mobile-button", action: "find" }, "Find"], ["span", { "class": "ace_mobile-button", action: "openCommandPallete" }, "Pallete"]] : ["span"]), y.firstChild) }, o = function (n) { var s = n.target.getAttribute("action"); if (s == "more" || !r) return r = !r, i(); if (s == "paste") e.readText().then(function (e) { t.execCommand(s, e) }); else if (s) { if (s == "cut" || s == "copy") e ? e.writeText(t.getCopyText()) : document.execCommand("copy"); t.execCommand(s) } y.firstChild.style.display = "none", r = !1, s != "openCommandPallete" && t.focus() }; y = s.buildDom(["div", { "class": "ace_mobile-menu", ontouchstart: function (e) { n = "menu", e.stopPropagation(), e.preventDefault(), t.textInput.focus() }, ontouchend: function (e) { e.stopPropagation(), e.preventDefault(), o(e) }, onclick: o }, ["span"], ["span", { "class": "ace_mobile-button", action: "more" }, "..."]], t.container) } function w() { y || b(); var e = t.selection.cursor, n = t.renderer.textToScreenCoordinates(e.row, e.column), r = t.container.getBoundingClientRect(); y.style.top = n.pageY - r.top - 3 + "px", y.style.right = "10px", y.style.display = "", y.firstChild.style.display = "none", t.on("input", E) } function E(e) { y && (y.style.display = "none"), t.off("input", E) } function S() { l = null, clearTimeout(l); var e = t.selection.getRange(), r = e.contains(p.row, p.column); if (e.isEmpty() || !r) t.selection.moveToPosition(p), t.selection.selectWord(); n = "wait", w() } function x() { l = null, clearTimeout(l), t.selection.moveToPosition(p); var e = d >= 2 ? t.selection.getLineRange(p.row) : t.session.getBracketRange(p); e && !e.isEmpty() ? t.selection.setRange(e) : t.selection.selectWord(), n = "wait" } function T() { h += 60, c = setInterval(function () { h-- <= 0 && (clearInterval(c), c = null), Math.abs(v) < .01 && (v = 0), Math.abs(m) < .01 && (m = 0), h < 20 && (v = .9 * v), h < 20 && (m = .9 * m); var e = t.session.getScrollTop(); t.renderer.scrollBy(10 * v, 10 * m), e == t.session.getScrollTop() && (h = 0) }, 10) } var n = "scroll", o, u, a, f, l, c, h = 0, p, d = 0, v = 0, m = 0, g, y; i.addListener(e, "contextmenu", function (e) { if (!g) return; var n = t.textInput.getElement(); n.focus() }, t), i.addListener(e, "touchstart", function (e) { var i = e.touches; if (l || i.length > 1) { clearTimeout(l), l = null, a = -1, n = "zoom"; return } g = t.$mouseHandler.isMousePressed = !0; var s = t.renderer.layerConfig.lineHeight, c = t.renderer.layerConfig.lineHeight, y = e.timeStamp; f = y; var b = i[0], w = b.clientX, E = b.clientY; Math.abs(o - w) + Math.abs(u - E) > s && (a = -1), o = e.clientX = w, u = e.clientY = E, v = m = 0; var T = new r(e, t); p = T.getDocumentPosition(); if (y - a < 500 && i.length == 1 && !h) d++, e.preventDefault(), e.button = 0, x(); else { d = 0; var N = t.selection.cursor, C = t.selection.isEmpty() ? N : t.selection.anchor, k = t.renderer.$cursorLayer.getPixelPosition(N, !0), L = t.renderer.$cursorLayer.getPixelPosition(C, !0), A = t.renderer.scroller.getBoundingClientRect(), O = function (e, t) { return e /= c, t = t / s - .75, e * e + t * t }; if (e.clientX < A.left) { n = "zoom"; return } var M = O(e.clientX - A.left - k.left, e.clientY - A.top - k.top), _ = O(e.clientX - A.left - L.left, e.clientY - A.top - L.top); M < 3.5 && _ < 3.5 && (n = M > _ ? "cursor" : "anchor"), _ < 3.5 ? n = "anchor" : M < 3.5 ? n = "cursor" : n = "scroll", l = setTimeout(S, 450) } a = y }, t), i.addListener(e, "touchend", function (e) { g = t.$mouseHandler.isMousePressed = !1, c && clearInterval(c), n == "zoom" ? (n = "", h = 0) : l ? (t.selection.moveToPosition(p), h = 0, w()) : n == "scroll" ? (T(), E()) : w(), clearTimeout(l), l = null }, t), i.addListener(e, "touchmove", function (e) { l && (clearTimeout(l), l = null); var i = e.touches; if (i.length > 1 || n == "zoom") return; var s = i[0], a = o - s.clientX, c = u - s.clientY; if (n == "wait") { if (!(a * a + c * c > 4)) return e.preventDefault(); n = "cursor" } o = s.clientX, u = s.clientY, e.clientX = s.clientX, e.clientY = s.clientY; var h = e.timeStamp, p = h - f; f = h; if (n == "scroll") { var d = new r(e, t); d.speed = 1, d.wheelX = a, d.wheelY = c, 10 * Math.abs(a) < Math.abs(c) && (a = 0), 10 * Math.abs(c) < Math.abs(a) && (c = 0), p != 0 && (v = a / p, m = c / p), t._emit("mousewheel", d), d.propagationStopped || (v = m = 0) } else { var g = new r(e, t), y = g.getDocumentPosition(); n == "cursor" ? t.selection.moveCursorToPosition(y) : n == "anchor" && t.selection.setSelectionAnchor(y.row, y.column), t.renderer.scrollCursorIntoView(y), e.preventDefault() } }, t) } }), define("ace/lib/net", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./dom"); t.get = function (e, t) { var n = new XMLHttpRequest; n.open("GET", e, !0), n.onreadystatechange = function () { n.readyState === 4 && t(n.responseText) }, n.send(null) }, t.loadScript = function (e, t) { var n = r.getDocumentHead(), i = document.createElement("script"); i.src = e, n.appendChild(i), i.onload = i.onreadystatechange = function (e, n) { if (n || !i.readyState || i.readyState == "loaded" || i.readyState == "complete") i = i.onload = i.onreadystatechange = null, n || t() } }, t.qualifyURL = function (e) { var t = document.createElement("a"); return t.href = e, t.href } }), define("ace/lib/event_emitter", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = {}, i = function () { this.propagationStopped = !0 }, s = function () { this.defaultPrevented = !0 }; r._emit = r._dispatchEvent = function (e, t) { this._eventRegistry || (this._eventRegistry = {}), this._defaultHandlers || (this._defaultHandlers = {}); var n = this._eventRegistry[e] || [], r = this._defaultHandlers[e]; if (!n.length && !r) return; if (typeof t != "object" || !t) t = {}; t.type || (t.type = e), t.stopPropagation || (t.stopPropagation = i), t.preventDefault || (t.preventDefault = s), n = n.slice(); for (var o = 0; o < n.length; o++) { n[o](t, this); if (t.propagationStopped) break } if (r && !t.defaultPrevented) return r(t, this) }, r._signal = function (e, t) { var n = (this._eventRegistry || {})[e]; if (!n) return; n = n.slice(); for (var r = 0; r < n.length; r++)n[r](t, this) }, r.once = function (e, t) { var n = this; this.on(e, function r() { n.off(e, r), t.apply(null, arguments) }); if (!t) return new Promise(function (e) { t = e }) }, r.setDefaultHandler = function (e, t) { var n = this._defaultHandlers; n || (n = this._defaultHandlers = { _disabled_: {} }); if (n[e]) { var r = n[e], i = n._disabled_[e]; i || (n._disabled_[e] = i = []), i.push(r); var s = i.indexOf(t); s != -1 && i.splice(s, 1) } n[e] = t }, r.removeDefaultHandler = function (e, t) { var n = this._defaultHandlers; if (!n) return; var r = n._disabled_[e]; if (n[e] == t) r && this.setDefaultHandler(e, r.pop()); else if (r) { var i = r.indexOf(t); i != -1 && r.splice(i, 1) } }, r.on = r.addEventListener = function (e, t, n) { this._eventRegistry = this._eventRegistry || {}; var r = this._eventRegistry[e]; return r || (r = this._eventRegistry[e] = []), r.indexOf(t) == -1 && r[n ? "unshift" : "push"](t), t }, r.off = r.removeListener = r.removeEventListener = function (e, t) { this._eventRegistry = this._eventRegistry || {}; var n = this._eventRegistry[e]; if (!n) return; var r = n.indexOf(t); r !== -1 && n.splice(r, 1) }, r.removeAllListeners = function (e) { e || (this._eventRegistry = this._defaultHandlers = undefined), this._eventRegistry && (this._eventRegistry[e] = undefined), this._defaultHandlers && (this._defaultHandlers[e] = undefined) }, t.EventEmitter = r }), define("ace/lib/app_config", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "no use strict"; function o(e) { typeof console != "undefined" && console.warn && console.warn.apply(console, arguments) } function u(e, t) { var n = new Error(e); n.data = t, typeof console == "object" && console.error && console.error(n), setTimeout(function () { throw n }) } var r = e("./oop"), i = e("./event_emitter").EventEmitter, s = { setOptions: function (e) { Object.keys(e).forEach(function (t) { this.setOption(t, e[t]) }, this) }, getOptions: function (e) { var t = {}; if (!e) { var n = this.$options; e = Object.keys(n).filter(function (e) { return !n[e].hidden }) } else Array.isArray(e) || (t = e, e = Object.keys(t)); return e.forEach(function (e) { t[e] = this.getOption(e) }, this), t }, setOption: function (e, t) { if (this["$" + e] === t) return; var n = this.$options[e]; if (!n) return o('misspelled option "' + e + '"'); if (n.forwardTo) return this[n.forwardTo] && this[n.forwardTo].setOption(e, t); n.handlesSet || (this["$" + e] = t), n && n.set && n.set.call(this, t) }, getOption: function (e) { var t = this.$options[e]; return t ? t.forwardTo ? this[t.forwardTo] && this[t.forwardTo].getOption(e) : t && t.get ? t.get.call(this) : this["$" + e] : o('misspelled option "' + e + '"') } }, a = function () { this.$defaultOptions = {} }; (function () { r.implement(this, i), this.defineOptions = function (e, t, n) { return e.$options || (this.$defaultOptions[t] = e.$options = {}), Object.keys(n).forEach(function (t) { var r = n[t]; typeof r == "string" && (r = { forwardTo: r }), r.name || (r.name = t), e.$options[r.name] = r, "initialValue" in r && (e["$" + r.name] = r.initialValue) }), r.implement(e, s), this }, this.resetOptions = function (e) { Object.keys(e.$options).forEach(function (t) { var n = e.$options[t]; "value" in n && e.setOption(t, n.value) }) }, this.setDefaultValue = function (e, t, n) { if (!e) { for (e in this.$defaultOptions) if (this.$defaultOptions[e][t]) break; if (!this.$defaultOptions[e][t]) return !1 } var r = this.$defaultOptions[e] || (this.$defaultOptions[e] = {}); r[t] && (r.forwardTo ? this.setDefaultValue(r.forwardTo, t, n) : r[t].value = n) }, this.setDefaultValues = function (e, t) { Object.keys(t).forEach(function (n) { this.setDefaultValue(e, n, t[n]) }, this) }, this.warn = o, this.reportError = u }).call(a.prototype), t.AppConfig = a }), define("ace/config", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/lib/net", "ace/lib/app_config"], function (e, t, n) { "no use strict"; function l(r) { if (!u || !u.document) return; a.packaged = r || e.packaged || n.packaged || u.define && define.packaged; var i = {}, s = "", o = document.currentScript || document._currentScript, f = o && o.ownerDocument || document, l = f.getElementsByTagName("script"); for (var h = 0; h < l.length; h++) { var p = l[h], d = p.src || p.getAttribute("src"); if (!d) continue; var v = p.attributes; for (var m = 0, g = v.length; m < g; m++) { var y = v[m]; y.name.indexOf("data-ace-") === 0 && (i[c(y.name.replace(/^data-ace-/, ""))] = y.value) } var b = d.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/); b && (s = b[1]) } s && (i.base = i.base || s, i.packaged = !0), i.basePath = i.base, i.workerPath = i.workerPath || i.base, i.modePath = i.modePath || i.base, i.themePath = i.themePath || i.base, delete i.base; for (var w in i) typeof i[w] != "undefined" && t.set(w, i[w]) } function c(e) { return e.replace(/-(.)/g, function (e, t) { return t.toUpperCase() }) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./lib/net"), o = e("./lib/app_config").AppConfig; n.exports = t = new o; var u = function () { return this || typeof window != "undefined" && window }(), a = { packaged: !1, workerPath: null, modePath: null, themePath: null, basePath: "", suffix: ".js", $moduleUrls: {}, loadWorkerFromBlob: !0, sharedPopups: !1 }; t.get = function (e) { if (!a.hasOwnProperty(e)) throw new Error("Unknown config key: " + e); return a[e] }, t.set = function (e, t) { if (a.hasOwnProperty(e)) a[e] = t; else if (this.setDefaultValue("", e, t) == 0) throw new Error("Unknown config key: " + e) }, t.all = function () { return r.copyObject(a) }, t.$modes = {}, t.moduleUrl = function (e, t) { if (a.$moduleUrls[e]) return a.$moduleUrls[e]; var n = e.split("/"); t = t || n[n.length - 2] || ""; var r = t == "snippets" ? "/" : "-", i = n[n.length - 1]; if (t == "worker" && r == "-") { var s = new RegExp("^" + t + "[\\-_]|[\\-_]" + t + "$", "g"); i = i.replace(s, "") } (!i || i == t) && n.length > 1 && (i = n[n.length - 2]); var o = a[t + "Path"]; return o == null ? o = a.basePath : r == "/" && (t = r = ""), o && o.slice(-1) != "/" && (o += "/"), o + t + r + i + this.get("suffix") }, t.setModuleUrl = function (e, t) { return a.$moduleUrls[e] = t }, t.$loading = {}, t.loadModule = function (n, r) { var i, o; Array.isArray(n) && (o = n[0], n = n[1]); try { i = e(n) } catch (u) { } if (i && !t.$loading[n]) return r && r(i); t.$loading[n] || (t.$loading[n] = []), t.$loading[n].push(r); if (t.$loading[n].length > 1) return; var a = function () { e([n], function (e) { t._emit("load.module", { name: n, module: e }); var r = t.$loading[n]; t.$loading[n] = null, r.forEach(function (t) { t && t(e) }) }) }; if (!t.get("packaged")) return a(); s.loadScript(t.moduleUrl(n, o), a), f() }; var f = function () { !a.basePath && !a.workerPath && !a.modePath && !a.themePath && !Object.keys(a.$moduleUrls).length && (console.error("Unable to infer path to ace from script src,", "use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes", "or with webpack use ace/webpack-resolver"), f = function () { }) }; t.init = l, t.version = "1.4.10" }), define("ace/mouse/mouse_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/mouse/default_handlers", "ace/mouse/default_gutter_handler", "ace/mouse/mouse_event", "ace/mouse/dragdrop_handler", "ace/mouse/touch_handler", "ace/config"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("./default_handlers").DefaultHandlers, o = e("./default_gutter_handler").GutterHandler, u = e("./mouse_event").MouseEvent, a = e("./dragdrop_handler").DragdropHandler, f = e("./touch_handler").addTouchListeners, l = e("../config"), c = function (e) { var t = this; this.editor = e, new s(this), new o(this), new a(this); var n = function (t) { var n = !document.hasFocus || !document.hasFocus() || !e.isFocused() && document.activeElement == (e.textInput && e.textInput.getElement()); n && window.focus(), e.focus() }, u = e.renderer.getMouseEventTarget(); r.addListener(u, "click", this.onMouseEvent.bind(this, "click"), e), r.addListener(u, "mousemove", this.onMouseMove.bind(this, "mousemove"), e), r.addMultiMouseDownListener([u, e.renderer.scrollBarV && e.renderer.scrollBarV.inner, e.renderer.scrollBarH && e.renderer.scrollBarH.inner, e.textInput && e.textInput.getElement()].filter(Boolean), [400, 300, 250], this, "onMouseEvent", e), r.addMouseWheelListener(e.container, this.onMouseWheel.bind(this, "mousewheel"), e), f(e.container, e); var l = e.renderer.$gutter; r.addListener(l, "mousedown", this.onMouseEvent.bind(this, "guttermousedown"), e), r.addListener(l, "click", this.onMouseEvent.bind(this, "gutterclick"), e), r.addListener(l, "dblclick", this.onMouseEvent.bind(this, "gutterdblclick"), e), r.addListener(l, "mousemove", this.onMouseEvent.bind(this, "guttermousemove"), e), r.addListener(u, "mousedown", n, e), r.addListener(l, "mousedown", n, e), i.isIE && e.renderer.scrollBarV && (r.addListener(e.renderer.scrollBarV.element, "mousedown", n, e), r.addListener(e.renderer.scrollBarH.element, "mousedown", n, e)), e.on("mousemove", function (n) { if (t.state || t.$dragDelay || !t.$dragEnabled) return; var r = e.renderer.screenToTextCoordinates(n.x, n.y), i = e.session.selection.getRange(), s = e.renderer; !i.isEmpty() && i.insideStart(r.row, r.column) ? s.setCursorStyle("default") : s.setCursorStyle("") }, e) }; (function () { this.onMouseEvent = function (e, t) { this.editor._emit(e, new u(t, this.editor)) }, this.onMouseMove = function (e, t) { var n = this.editor._eventRegistry && this.editor._eventRegistry.mousemove; if (!n || !n.length) return; this.editor._emit(e, new u(t, this.editor)) }, this.onMouseWheel = function (e, t) { var n = new u(t, this.editor); n.speed = this.$scrollSpeed * 2, n.wheelX = t.wheelX, n.wheelY = t.wheelY, this.editor._emit(e, n) }, this.setState = function (e) { this.state = e }, this.captureMouse = function (e, t) { this.x = e.x, this.y = e.y, this.isMousePressed = !0; var n = this.editor, s = this.editor.renderer; s.$isMousePressed = !0; var o = this, a = function (e) { if (!e) return; if (i.isWebKit && !e.which && o.releaseMouse) return o.releaseMouse(); o.x = e.clientX, o.y = e.clientY, t && t(e), o.mouseEvent = new u(e, o.editor), o.$mouseMoved = !0 }, f = function (e) { n.off("beforeEndOperation", c), clearInterval(h), l(), o[o.state + "End"] && o[o.state + "End"](e), o.state = "", o.isMousePressed = s.$isMousePressed = !1, s.$keepTextAreaAtCursor && s.$moveTextAreaToCursor(), o.$onCaptureMouseMove = o.releaseMouse = null, e && o.onMouseEvent("mouseup", e), n.endOperation() }, l = function () { o[o.state] && o[o.state](), o.$mouseMoved = !1 }; if (i.isOldIE && e.domEvent.type == "dblclick") return setTimeout(function () { f(e) }); var c = function (e) { if (!o.releaseMouse) return; n.curOp.command.name && n.curOp.selectionChanged && (o[o.state + "End"] && o[o.state + "End"](), o.state = "", o.releaseMouse()) }; n.on("beforeEndOperation", c), n.startOperation({ command: { name: "mouse" } }), o.$onCaptureMouseMove = a, o.releaseMouse = r.capture(this.editor.container, a, f); var h = setInterval(l, 20) }, this.releaseMouse = null, this.cancelContextMenu = function () { var e = function (t) { if (t && t.domEvent && t.domEvent.type != "contextmenu") return; this.editor.off("nativecontextmenu", e), t && t.domEvent && r.stopEvent(t.domEvent) }.bind(this); setTimeout(e, 10), this.editor.on("nativecontextmenu", e) } }).call(c.prototype), l.defineOptions(c.prototype, "mouseHandler", { scrollSpeed: { initialValue: 2 }, dragDelay: { initialValue: i.isMac ? 150 : 0 }, dragEnabled: { initialValue: !0 }, focusTimeout: { initialValue: 0 }, tooltipFollowsMouse: { initialValue: !0 } }), t.MouseHandler = c }), define("ace/mouse/fold_handler", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { e.on("click", function (t) { var n = t.getDocumentPosition(), i = e.session, s = i.getFoldAt(n.row, n.column, 1); s && (t.getAccelKey() ? i.removeFold(s) : i.expandFold(s), t.stop()); var o = t.domEvent && t.domEvent.target; o && r.hasCssClass(o, "ace_inline_button") && r.hasCssClass(o, "ace_toggle_wrap") && (i.setOption("wrap", !i.getUseWrapMode()), e.renderer.scrollCursorIntoView()) }), e.on("gutterclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session; i.foldWidgets && i.foldWidgets[r] && e.session.onFoldWidgetClick(r, t), e.isFocused() || e.focus(), t.stop() } }), e.on("gutterdblclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session, s = i.getParentFoldRangeData(r, !0), o = s.range || s.firstRange; if (o) { r = o.start.row; var u = i.getFoldAt(r, i.getLine(r).length, 1); u ? i.removeFold(u) : (i.addFold("...", o), e.renderer.scrollCursorIntoView({ row: o.start.row, column: 0 })) } t.stop() } }) } var r = e("../lib/dom"); t.FoldHandler = i }), define("ace/keyboard/keybinding", ["require", "exports", "module", "ace/lib/keys", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("../lib/keys"), i = e("../lib/event"), s = function (e) { this.$editor = e, this.$data = { editor: e }, this.$handlers = [], this.setDefaultHandler(e.commands) }; (function () { this.setDefaultHandler = function (e) { this.removeKeyboardHandler(this.$defaultHandler), this.$defaultHandler = e, this.addKeyboardHandler(e, 0) }, this.setKeyboardHandler = function (e) { var t = this.$handlers; if (t[t.length - 1] == e) return; while (t[t.length - 1] && t[t.length - 1] != this.$defaultHandler) this.removeKeyboardHandler(t[t.length - 1]); this.addKeyboardHandler(e, 1) }, this.addKeyboardHandler = function (e, t) { if (!e) return; typeof e == "function" && !e.handleKeyboard && (e.handleKeyboard = e); var n = this.$handlers.indexOf(e); n != -1 && this.$handlers.splice(n, 1), t == undefined ? this.$handlers.push(e) : this.$handlers.splice(t, 0, e), n == -1 && e.attach && e.attach(this.$editor) }, this.removeKeyboardHandler = function (e) { var t = this.$handlers.indexOf(e); return t == -1 ? !1 : (this.$handlers.splice(t, 1), e.detach && e.detach(this.$editor), !0) }, this.getKeyboardHandler = function () { return this.$handlers[this.$handlers.length - 1] }, this.getStatusText = function () { var e = this.$data, t = e.editor; return this.$handlers.map(function (n) { return n.getStatusText && n.getStatusText(t, e) || "" }).filter(Boolean).join(" ") }, this.$callKeyboardHandlers = function (e, t, n, r) { var s, o = !1, u = this.$editor.commands; for (var a = this.$handlers.length; a--;) { s = this.$handlers[a].handleKeyboard(this.$data, e, t, n, r); if (!s || !s.command) continue; s.command == "null" ? o = !0 : o = u.exec(s.command, this.$editor, s.args, r), o && r && e != -1 && s.passEvent != 1 && s.command.passEvent != 1 && i.stopEvent(r); if (o) break } return !o && e == -1 && (s = { command: "insertstring" }, o = u.exec("insertstring", this.$editor, t)), o && this.$editor._signal && this.$editor._signal("keyboardActivity", s), o }, this.onCommandKey = function (e, t, n) { var i = r.keyCodeToString(n); return this.$callKeyboardHandlers(t, i, n, e) }, this.onTextInput = function (e) { return this.$callKeyboardHandlers(-1, e) } }).call(s.prototype), t.KeyBinding = s }), define("ace/lib/bidiutil", ["require", "exports", "module"], function (e, t, n) { "use strict"; function F(e, t, n, r) { var i = s ? d : p, c = null, h = null, v = null, m = 0, g = null, y = null, b = -1, w = null, E = null, T = []; if (!r) for (w = 0, r = []; w < n; w++)r[w] = R(e[w]); o = s, u = !1, a = !1, f = !1, l = !1; for (E = 0; E < n; E++) { c = m, T[E] = h = q(e, r, T, E), m = i[c][h], g = m & 240, m &= 15, t[E] = v = i[m][5]; if (g > 0) if (g == 16) { for (w = b; w < E; w++)t[w] = 1; b = -1 } else b = -1; y = i[m][6]; if (y) b == -1 && (b = E); else if (b > -1) { for (w = b; w < E; w++)t[w] = v; b = -1 } r[E] == S && (t[E] = 0), o |= v } if (l) for (w = 0; w < n; w++)if (r[w] == x) { t[w] = s; for (var C = w - 1; C >= 0; C--) { if (r[C] != N) break; t[C] = s } } } function I(e, t, n) { if (o < e) return; if (e == 1 && s == m && !f) { n.reverse(); return } var r = n.length, i = 0, u, a, l, c; while (i < r) { if (t[i] >= e) { u = i + 1; while (u < r && t[u] >= e) u++; for (a = i, l = u - 1; a < l; a++, l--)c = n[a], n[a] = n[l], n[l] = c; i = u } i++ } } function q(e, t, n, r) { var i = t[r], o, c, h, p; switch (i) { case g: case y: u = !1; case E: case w: return i; case b: return u ? w : b; case T: return u = !0, a = !0, y; case N: return E; case C: if (r < 1 || r + 1 >= t.length || (o = n[r - 1]) != b && o != w || (c = t[r + 1]) != b && c != w) return E; return u && (c = w), c == o ? c : E; case k: o = r > 0 ? n[r - 1] : S; if (o == b && r + 1 < t.length && t[r + 1] == b) return b; return E; case L: if (r > 0 && n[r - 1] == b) return b; if (u) return E; p = r + 1, h = t.length; while (p < h && t[p] == L) p++; if (p < h && t[p] == b) return b; return E; case A: h = t.length, p = r + 1; while (p < h && t[p] == A) p++; if (p < h) { var d = e[r], v = d >= 1425 && d <= 2303 || d == 64286; o = t[p]; if (v && (o == y || o == T)) return y } if (r < 1 || (o = t[r - 1]) == S) return E; return n[r - 1]; case S: return u = !1, f = !0, s; case x: return l = !0, E; case O: case M: case D: case P: case _: u = !1; case H: return E } } function R(e) { var t = e.charCodeAt(0), n = t >> 8; return n == 0 ? t > 191 ? g : B[t] : n == 5 ? /[\u0591-\u05f4]/.test(e) ? y : g : n == 6 ? /[\u0610-\u061a\u064b-\u065f\u06d6-\u06e4\u06e7-\u06ed]/.test(e) ? A : /[\u0660-\u0669\u066b-\u066c]/.test(e) ? w : t == 1642 ? L : /[\u06f0-\u06f9]/.test(e) ? b : T : n == 32 && t <= 8287 ? j[t & 255] : n == 254 ? t >= 65136 ? T : E : E } function U(e) { return e >= "\u064b" && e <= "\u0655" } var r = ["\u0621", "\u0641"], i = ["\u063a", "\u064a"], s = 0, o = 0, u = !1, a = !1, f = !1, l = !1, c = !1, h = !1, p = [[0, 3, 0, 1, 0, 0, 0], [0, 3, 0, 1, 2, 2, 0], [0, 3, 0, 17, 2, 0, 1], [0, 3, 5, 5, 4, 1, 0], [0, 3, 21, 21, 4, 0, 1], [0, 3, 5, 5, 4, 2, 0]], d = [[2, 0, 1, 1, 0, 1, 0], [2, 0, 1, 1, 0, 2, 0], [2, 0, 2, 1, 3, 2, 0], [2, 0, 2, 33, 3, 1, 1]], v = 0, m = 1, g = 0, y = 1, b = 2, w = 3, E = 4, S = 5, x = 6, T = 7, N = 8, C = 9, k = 10, L = 11, A = 12, O = 13, M = 14, _ = 15, D = 16, P = 17, H = 18, B = [H, H, H, H, H, H, H, H, H, x, S, x, N, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, S, S, S, x, N, E, E, L, L, L, E, E, E, E, E, k, C, k, C, C, b, b, b, b, b, b, b, b, b, b, C, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, H, H, H, H, H, H, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, C, E, L, L, L, L, E, E, E, E, g, E, E, H, E, E, L, L, b, b, E, g, E, E, E, b, g, E, E, E, E, E], j = [N, N, N, N, N, N, N, N, N, N, N, H, H, H, g, y, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N, S, O, M, _, D, P, C, L, L, L, L, L, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, C, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N]; t.L = g, t.R = y, t.EN = b, t.ON_R = 3, t.AN = 4, t.R_H = 5, t.B = 6, t.RLE = 7, t.DOT = "\u00b7", t.doBidiReorder = function (e, n, r) { if (e.length < 2) return {}; var i = e.split(""), o = new Array(i.length), u = new Array(i.length), a = []; s = r ? m : v, F(i, a, i.length, n); for (var f = 0; f < o.length; o[f] = f, f++); I(2, a, o), I(1, a, o); for (var f = 0; f < o.length - 1; f++)n[f] === w ? a[f] = t.AN : a[f] === y && (n[f] > T && n[f] < O || n[f] === E || n[f] === H) ? a[f] = t.ON_R : f > 0 && i[f - 1] === "\u0644" && /\u0622|\u0623|\u0625|\u0627/.test(i[f]) && (a[f - 1] = a[f] = t.R_H, f++); i[i.length - 1] === t.DOT && (a[i.length - 1] = t.B), i[0] === "\u202b" && (a[0] = t.RLE); for (var f = 0; f < o.length; f++)u[f] = a[o[f]]; return { logicalFromVisual: o, bidiLevels: u } }, t.hasBidiCharacters = function (e, t) { var n = !1; for (var r = 0; r < e.length; r++)t[r] = R(e.charAt(r)), !n && (t[r] == y || t[r] == T || t[r] == w) && (n = !0); return n }, t.getVisualFromLogicalIdx = function (e, t) { for (var n = 0; n < t.logicalFromVisual.length; n++)if (t.logicalFromVisual[n] == e) return n; return 0 } }), define("ace/bidihandler", ["require", "exports", "module", "ace/lib/bidiutil", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("./lib/bidiutil"), i = e("./lib/lang"), s = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\u202B]/, o = function (e) { this.session = e, this.bidiMap = {}, this.currentRow = null, this.bidiUtil = r, this.charWidths = [], this.EOL = "\u00ac", this.showInvisibles = !0, this.isRtlDir = !1, this.$isRtl = !1, this.line = "", this.wrapIndent = 0, this.EOF = "\u00b6", this.RLE = "\u202b", this.contentWidth = 0, this.fontMetrics = null, this.rtlLineOffset = 0, this.wrapOffset = 0, this.isMoveLeftOperation = !1, this.seenBidi = s.test(e.getValue()) }; (function () { this.isBidiRow = function (e, t, n) { return this.seenBidi ? (e !== this.currentRow && (this.currentRow = e, this.updateRowLine(t, n), this.updateBidiMap()), this.bidiMap.bidiLevels) : !1 }, this.onChange = function (e) { this.seenBidi ? this.currentRow = null : e.action == "insert" && s.test(e.lines.join("\n")) && (this.seenBidi = !0, this.currentRow = null) }, this.getDocumentRow = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n = this.session.$getRowCacheIndex(t, this.currentRow); n >= 0 && (e = this.session.$docRowCache[n]) } return e }, this.getSplitIndex = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n, r = this.session.$getRowCacheIndex(t, this.currentRow); while (this.currentRow - e > 0) { n = this.session.$getRowCacheIndex(t, this.currentRow - e - 1); if (n !== r) break; r = n, e++ } } else e = this.currentRow; return e }, this.updateRowLine = function (e, t) { e === undefined && (e = this.getDocumentRow()); var n = e === this.session.getLength() - 1, s = n ? this.EOF : this.EOL; this.wrapIndent = 0, this.line = this.session.getLine(e), this.isRtlDir = this.$isRtl || this.line.charAt(0) === this.RLE; if (this.session.$useWrapMode) { var o = this.session.$wrapData[e]; o && (t === undefined && (t = this.getSplitIndex()), t > 0 && o.length ? (this.wrapIndent = o.indent, this.wrapOffset = this.wrapIndent * this.charWidths[r.L], this.line = t < o.length ? this.line.substring(o[t - 1], o[t]) : this.line.substring(o[o.length - 1])) : this.line = this.line.substring(0, o[t])), t == o.length && (this.line += this.showInvisibles ? s : r.DOT) } else this.line += this.showInvisibles ? s : r.DOT; var u = this.session, a = 0, f; this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function (e, t) { return e === " " || u.isFullWidth(e.charCodeAt(0)) ? (f = e === " " ? u.getScreenTabSize(t + a) : 2, a += f - 1, i.stringRepeat(r.DOT, f)) : e }), this.isRtlDir && (this.fontMetrics.$main.textContent = this.line.charAt(this.line.length - 1) == r.DOT ? this.line.substr(0, this.line.length - 1) : this.line, this.rtlLineOffset = this.contentWidth - this.fontMetrics.$main.getBoundingClientRect().width) }, this.updateBidiMap = function () { var e = []; r.hasBidiCharacters(this.line, e) || this.isRtlDir ? this.bidiMap = r.doBidiReorder(this.line, e, this.isRtlDir) : this.bidiMap = {} }, this.markAsDirty = function () { this.currentRow = null }, this.updateCharacterWidths = function (e) { if (this.characterWidth === e.$characterSize.width) return; this.fontMetrics = e; var t = this.characterWidth = e.$characterSize.width, n = e.$measureCharWidth("\u05d4"); this.charWidths[r.L] = this.charWidths[r.EN] = this.charWidths[r.ON_R] = t, this.charWidths[r.R] = this.charWidths[r.AN] = n, this.charWidths[r.R_H] = n * .45, this.charWidths[r.B] = this.charWidths[r.RLE] = 0, this.currentRow = null }, this.setShowInvisibles = function (e) { this.showInvisibles = e, this.currentRow = null }, this.setEolChar = function (e) { this.EOL = e }, this.setContentWidth = function (e) { this.contentWidth = e }, this.isRtlLine = function (e) { return this.$isRtl ? !0 : e != undefined ? this.session.getLine(e).charAt(0) == this.RLE : this.isRtlDir }, this.setRtlDirection = function (e, t) { var n = e.getCursorPosition(); for (var r = e.selection.getSelectionAnchor().row; r <= n.row; r++)!t && e.session.getLine(r).charAt(0) === e.session.$bidiHandler.RLE ? e.session.doc.removeInLine(r, 0, 1) : t && e.session.getLine(r).charAt(0) !== e.session.$bidiHandler.RLE && e.session.doc.insert({ column: 0, row: r }, e.session.$bidiHandler.RLE) }, this.getPosLeft = function (e) { e -= this.wrapIndent; var t = this.line.charAt(0) === this.RLE ? 1 : 0, n = e > t ? this.session.getOverwrite() ? e : e - 1 : t, i = r.getVisualFromLogicalIdx(n, this.bidiMap), s = this.bidiMap.bidiLevels, o = 0; !this.session.getOverwrite() && e <= t && s[i] % 2 !== 0 && i++; for (var u = 0; u < i; u++)o += this.charWidths[s[u]]; return !this.session.getOverwrite() && e > t && s[i] % 2 === 0 && (o += this.charWidths[s[i]]), this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset), this.isRtlDir && (o += this.rtlLineOffset), o }, this.getSelections = function (e, t) { var n = this.bidiMap, r = n.bidiLevels, i, s = [], o = 0, u = Math.min(e, t) - this.wrapIndent, a = Math.max(e, t) - this.wrapIndent, f = !1, l = !1, c = 0; this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); for (var h, p = 0; p < r.length; p++)h = n.logicalFromVisual[p], i = r[p], f = h >= u && h < a, f && !l ? c = o : !f && l && s.push({ left: c, width: o - c }), o += this.charWidths[i], l = f; f && p === r.length && s.push({ left: c, width: o - c }); if (this.isRtlDir) for (var d = 0; d < s.length; d++)s[d].left += this.rtlLineOffset; return s }, this.offsetToCol = function (e) { this.isRtlDir && (e -= this.rtlLineOffset); var t = 0, e = Math.max(e, 0), n = 0, r = 0, i = this.bidiMap.bidiLevels, s = this.charWidths[i[r]]; this.wrapIndent && (e -= this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); while (e > n + s / 2) { n += s; if (r === i.length - 1) { s = 0; break } s = this.charWidths[i[++r]] } return r > 0 && i[r - 1] % 2 !== 0 && i[r] % 2 === 0 ? (e < n && r--, t = this.bidiMap.logicalFromVisual[r]) : r > 0 && i[r - 1] % 2 === 0 && i[r] % 2 !== 0 ? t = 1 + (e > n ? this.bidiMap.logicalFromVisual[r] : this.bidiMap.logicalFromVisual[r - 1]) : this.isRtlDir && r === i.length - 1 && s === 0 && i[r - 1] % 2 === 0 || !this.isRtlDir && r === 0 && i[r] % 2 !== 0 ? t = 1 + this.bidiMap.logicalFromVisual[r] : (r > 0 && i[r - 1] % 2 !== 0 && s !== 0 && r--, t = this.bidiMap.logicalFromVisual[r]), t === 0 && this.isRtlDir && t++, t + this.wrapIndent } }).call(o.prototype), t.BidiHandler = o }), define("ace/selection", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = function (e) { this.session = e, this.doc = e.getDocument(), this.clearSelection(), this.cursor = this.lead = this.doc.createAnchor(0, 0), this.anchor = this.doc.createAnchor(0, 0), this.$silent = !1; var t = this; this.cursor.on("change", function (e) { t.$cursorChanged = !0, t.$silent || t._emit("changeCursor"), !t.$isEmpty && !t.$silent && t._emit("changeSelection"), !t.$keepDesiredColumnOnChange && e.old.column != e.value.column && (t.$desiredColumn = null) }), this.anchor.on("change", function () { t.$anchorChanged = !0, !t.$isEmpty && !t.$silent && t._emit("changeSelection") }) }; (function () { r.implement(this, s), this.isEmpty = function () { return this.$isEmpty || this.anchor.row == this.lead.row && this.anchor.column == this.lead.column }, this.isMultiLine = function () { return !this.$isEmpty && this.anchor.row != this.cursor.row }, this.getCursor = function () { return this.lead.getPosition() }, this.setSelectionAnchor = function (e, t) { this.$isEmpty = !1, this.anchor.setPosition(e, t) }, this.getAnchor = this.getSelectionAnchor = function () { return this.$isEmpty ? this.getSelectionLead() : this.anchor.getPosition() }, this.getSelectionLead = function () { return this.lead.getPosition() }, this.isBackwards = function () { var e = this.anchor, t = this.lead; return e.row > t.row || e.row == t.row && e.column > t.column }, this.getRange = function () { var e = this.anchor, t = this.lead; return this.$isEmpty ? o.fromPoints(t, t) : this.isBackwards() ? o.fromPoints(t, e) : o.fromPoints(e, t) }, this.clearSelection = function () { this.$isEmpty || (this.$isEmpty = !0, this._emit("changeSelection")) }, this.selectAll = function () { this.$setSelection(0, 0, Number.MAX_VALUE, Number.MAX_VALUE) }, this.setRange = this.setSelectionRange = function (e, t) { var n = t ? e.end : e.start, r = t ? e.start : e.end; this.$setSelection(n.row, n.column, r.row, r.column) }, this.$setSelection = function (e, t, n, r) { if (this.$silent) return; var i = this.$isEmpty, s = this.inMultiSelectMode; this.$silent = !0, this.$cursorChanged = this.$anchorChanged = !1, this.anchor.setPosition(e, t), this.cursor.setPosition(n, r), this.$isEmpty = !o.comparePoints(this.anchor, this.cursor), this.$silent = !1, this.$cursorChanged && this._emit("changeCursor"), (this.$cursorChanged || this.$anchorChanged || i != this.$isEmpty || s) && this._emit("changeSelection") }, this.$moveSelection = function (e) { var t = this.lead; this.$isEmpty && this.setSelectionAnchor(t.row, t.column), e.call(this) }, this.selectTo = function (e, t) { this.$moveSelection(function () { this.moveCursorTo(e, t) }) }, this.selectToPosition = function (e) { this.$moveSelection(function () { this.moveCursorToPosition(e) }) }, this.moveTo = function (e, t) { this.clearSelection(), this.moveCursorTo(e, t) }, this.moveToPosition = function (e) { this.clearSelection(), this.moveCursorToPosition(e) }, this.selectUp = function () { this.$moveSelection(this.moveCursorUp) }, this.selectDown = function () { this.$moveSelection(this.moveCursorDown) }, this.selectRight = function () { this.$moveSelection(this.moveCursorRight) }, this.selectLeft = function () { this.$moveSelection(this.moveCursorLeft) }, this.selectLineStart = function () { this.$moveSelection(this.moveCursorLineStart) }, this.selectLineEnd = function () { this.$moveSelection(this.moveCursorLineEnd) }, this.selectFileEnd = function () { this.$moveSelection(this.moveCursorFileEnd) }, this.selectFileStart = function () { this.$moveSelection(this.moveCursorFileStart) }, this.selectWordRight = function () { this.$moveSelection(this.moveCursorWordRight) }, this.selectWordLeft = function () { this.$moveSelection(this.moveCursorWordLeft) }, this.getWordRange = function (e, t) { if (typeof t == "undefined") { var n = e || this.lead; e = n.row, t = n.column } return this.session.getWordRange(e, t) }, this.selectWord = function () { this.setSelectionRange(this.getWordRange()) }, this.selectAWord = function () { var e = this.getCursor(), t = this.session.getAWordRange(e.row, e.column); this.setSelectionRange(t) }, this.getLineRange = function (e, t) { var n = typeof e == "number" ? e : this.lead.row, r, i = this.session.getFoldLine(n); return i ? (n = i.start.row, r = i.end.row) : r = n, t === !0 ? new o(n, 0, r, this.session.getLine(r).length) : new o(n, 0, r + 1, 0) }, this.selectLine = function () { this.setSelectionRange(this.getLineRange()) }, this.moveCursorUp = function () { this.moveCursorBy(-1, 0) }, this.moveCursorDown = function () { this.moveCursorBy(1, 0) }, this.wouldMoveIntoSoftTab = function (e, t, n) { var r = e.column, i = e.column + t; return n < 0 && (r = e.column - t, i = e.column), this.session.isTabStop(e) && this.doc.getLine(e.row).slice(r, i).split(" ").length - 1 == t }, this.moveCursorLeft = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, -1)) this.moveCursorTo(t.start.row, t.start.column); else if (e.column === 0) e.row > 0 && this.moveCursorTo(e.row - 1, this.doc.getLine(e.row - 1).length); else { var n = this.session.getTabSize(); this.wouldMoveIntoSoftTab(e, n, -1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, -n) : this.moveCursorBy(0, -1) } }, this.moveCursorRight = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, 1)) this.moveCursorTo(t.end.row, t.end.column); else if (this.lead.column == this.doc.getLine(this.lead.row).length) this.lead.row < this.doc.getLength() - 1 && this.moveCursorTo(this.lead.row + 1, 0); else { var n = this.session.getTabSize(), e = this.lead; this.wouldMoveIntoSoftTab(e, n, 1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, n) : this.moveCursorBy(0, 1) } }, this.moveCursorLineStart = function () { var e = this.lead.row, t = this.lead.column, n = this.session.documentToScreenRow(e, t), r = this.session.screenToDocumentPosition(n, 0), i = this.session.getDisplayLine(e, null, r.row, r.column), s = i.match(/^\s*/); s[0].length != t && !this.session.$useEmacsStyleLineStart && (r.column += s[0].length), this.moveCursorToPosition(r) }, this.moveCursorLineEnd = function () { var e = this.lead, t = this.session.getDocumentLastRowColumnPosition(e.row, e.column); if (this.lead.column == t.column) { var n = this.session.getLine(t.row); if (t.column == n.length) { var r = n.search(/\s+$/); r > 0 && (t.column = r) } } this.moveCursorTo(t.row, t.column) }, this.moveCursorFileEnd = function () { var e = this.doc.getLength() - 1, t = this.doc.getLine(e).length; this.moveCursorTo(e, t) }, this.moveCursorFileStart = function () { this.moveCursorTo(0, 0) }, this.moveCursorLongWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0; var i = this.session.getFoldAt(e, t, 1); if (i) { this.moveCursorTo(i.end.row, i.end.column); return } this.session.nonTokenRe.exec(r) && (t += this.session.nonTokenRe.lastIndex, this.session.nonTokenRe.lastIndex = 0, r = n.substring(t)); if (t >= n.length) { this.moveCursorTo(e, n.length), this.moveCursorRight(), e < this.doc.getLength() - 1 && this.moveCursorWordRight(); return } this.session.tokenRe.exec(r) && (t += this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.moveCursorLongWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) { this.moveCursorTo(n.start.row, n.start.column); return } var r = this.session.getFoldStringAt(e, t, -1); r == null && (r = this.doc.getLine(e).substring(0, t)); var s = i.stringReverse(r); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0, this.session.nonTokenRe.exec(s) && (t -= this.session.nonTokenRe.lastIndex, s = s.slice(this.session.nonTokenRe.lastIndex), this.session.nonTokenRe.lastIndex = 0); if (t <= 0) { this.moveCursorTo(e, 0), this.moveCursorLeft(), e > 0 && this.moveCursorWordLeft(); return } this.session.tokenRe.exec(s) && (t -= this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.$shortWordEndIndex = function (e) { var t = 0, n, r = /\s/, i = this.session.tokenRe; i.lastIndex = 0; if (this.session.tokenRe.exec(e)) t = this.session.tokenRe.lastIndex; else { while ((n = e[t]) && r.test(n)) t++; if (t < 1) { i.lastIndex = 0; while ((n = e[t]) && !i.test(n)) { i.lastIndex = 0, t++; if (r.test(n)) { if (t > 2) { t--; break } while ((n = e[t]) && r.test(n)) t++; if (t > 2) break } } } } return i.lastIndex = 0, t }, this.moveCursorShortWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t), i = this.session.getFoldAt(e, t, 1); if (i) return this.moveCursorTo(i.end.row, i.end.column); if (t == n.length) { var s = this.doc.getLength(); do e++, r = this.doc.getLine(e); while (e < s && /^\s*$/.test(r)); /^\s+/.test(r) || (r = ""), t = 0 } var o = this.$shortWordEndIndex(r); this.moveCursorTo(e, t + o) }, this.moveCursorShortWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) return this.moveCursorTo(n.start.row, n.start.column); var r = this.session.getLine(e).substring(0, t); if (t === 0) { do e--, r = this.doc.getLine(e); while (e > 0 && /^\s*$/.test(r)); t = r.length, /\s+$/.test(r) || (r = "") } var s = i.stringReverse(r), o = this.$shortWordEndIndex(s); return this.moveCursorTo(e, t - o) }, this.moveCursorWordRight = function () { this.session.$selectLongWords ? this.moveCursorLongWordRight() : this.moveCursorShortWordRight() }, this.moveCursorWordLeft = function () { this.session.$selectLongWords ? this.moveCursorLongWordLeft() : this.moveCursorShortWordLeft() }, this.moveCursorBy = function (e, t) { var n = this.session.documentToScreenPosition(this.lead.row, this.lead.column), r; t === 0 && (e !== 0 && (this.session.$bidiHandler.isBidiRow(n.row, this.lead.row) ? (r = this.session.$bidiHandler.getPosLeft(n.column), n.column = Math.round(r / this.session.$bidiHandler.charWidths[0])) : r = n.column * this.session.$bidiHandler.charWidths[0]), this.$desiredColumn ? n.column = this.$desiredColumn : this.$desiredColumn = n.column); if (e != 0 && this.session.lineWidgets && this.session.lineWidgets[this.lead.row]) { var i = this.session.lineWidgets[this.lead.row]; e < 0 ? e -= i.rowsAbove || 0 : e > 0 && (e += i.rowCount - (i.rowsAbove || 0)) } var s = this.session.screenToDocumentPosition(n.row + e, n.column, r); e !== 0 && t === 0 && s.row === this.lead.row && s.column === this.lead.column, this.moveCursorTo(s.row, s.column + t, t === 0) }, this.moveCursorToPosition = function (e) { this.moveCursorTo(e.row, e.column) }, this.moveCursorTo = function (e, t, n) { var r = this.session.getFoldAt(e, t, 1); r && (e = r.start.row, t = r.start.column), this.$keepDesiredColumnOnChange = !0; var i = this.session.getLine(e); /[\uDC00-\uDFFF]/.test(i.charAt(t)) && i.charAt(t - 1) && (this.lead.row == e && this.lead.column == t + 1 ? t -= 1 : t += 1), this.lead.setPosition(e, t), this.$keepDesiredColumnOnChange = !1, n || (this.$desiredColumn = null) }, this.moveCursorToScreen = function (e, t, n) { var r = this.session.screenToDocumentPosition(e, t); this.moveCursorTo(r.row, r.column, n) }, this.detach = function () { this.lead.detach(), this.anchor.detach(), this.session = this.doc = null }, this.fromOrientedRange = function (e) { this.setSelectionRange(e, e.cursor == e.start), this.$desiredColumn = e.desiredColumn || this.$desiredColumn }, this.toOrientedRange = function (e) { var t = this.getRange(); return e ? (e.start.column = t.start.column, e.start.row = t.start.row, e.end.column = t.end.column, e.end.row = t.end.row) : e = t, e.cursor = this.isBackwards() ? e.start : e.end, e.desiredColumn = this.$desiredColumn, e }, this.getRangeOfMovements = function (e) { var t = this.getCursor(); try { e(this); var n = this.getCursor(); return o.fromPoints(t, n) } catch (r) { return o.fromPoints(t, t) } finally { this.moveCursorToPosition(t) } }, this.toJSON = function () { if (this.rangeCount) var e = this.ranges.map(function (e) { var t = e.clone(); return t.isBackwards = e.cursor == e.start, t }); else { var e = this.getRange(); e.isBackwards = this.isBackwards() } return e }, this.fromJSON = function (e) { if (e.start == undefined) { if (this.rangeList && e.length > 1) { this.toSingleRange(e[0]); for (var t = e.length; t--;) { var n = o.fromPoints(e[t].start, e[t].end); e[t].isBackwards && (n.cursor = n.start), this.addRange(n, !0) } return } e = e[0] } this.rangeList && this.toSingleRange(e), this.setSelectionRange(e, e.isBackwards) }, this.isEqual = function (e) { if ((e.length || this.rangeCount) && e.length != this.rangeCount) return !1; if (!e.length || !this.ranges) return this.getRange().isEqual(e); for (var t = this.ranges.length; t--;)if (!this.ranges[t].isEqual(e[t])) return !1; return !0 } }).call(u.prototype), t.Selection = u }), define("ace/tokenizer", ["require", "exports", "module", "ace/config"], function (e, t, n) { "use strict"; var r = e("./config"), i = 2e3, s = function (e) { this.states = e, this.regExps = {}, this.matchMappings = {}; for (var t in this.states) { var n = this.states[t], r = [], i = 0, s = this.matchMappings[t] = { defaultToken: "text" }, o = "g", u = []; for (var a = 0; a < n.length; a++) { var f = n[a]; f.defaultToken && (s.defaultToken = f.defaultToken), f.caseInsensitive && (o = "gi"); if (f.regex == null) continue; f.regex instanceof RegExp && (f.regex = f.regex.toString().slice(1, -1)); var l = f.regex, c = (new RegExp("(?:(" + l + ")|(.))")).exec("a").length - 2; Array.isArray(f.token) ? f.token.length == 1 || c == 1 ? f.token = f.token[0] : c - 1 != f.token.length ? (this.reportError("number of classes and regexp groups doesn't match", { rule: f, groupCount: c - 1 }), f.token = f.token[0]) : (f.tokenArray = f.token, f.token = null, f.onMatch = this.$arrayTokens) : typeof f.token == "function" && !f.onMatch && (c > 1 ? f.onMatch = this.$applyToken : f.onMatch = f.token), c > 1 && (/\\\d/.test(f.regex) ? l = f.regex.replace(/\\([0-9]+)/g, function (e, t) { return "\\" + (parseInt(t, 10) + i + 1) }) : (c = 1, l = this.removeCapturingGroups(f.regex)), !f.splitRegex && typeof f.token != "string" && u.push(f)), s[i] = a, i += c, r.push(l), f.onMatch || (f.onMatch = null) } r.length || (s[0] = 0, r.push("$")), u.forEach(function (e) { e.splitRegex = this.createSplitterRegexp(e.regex, o) }, this), this.regExps[t] = new RegExp("(" + r.join(")|(") + ")|($)", o) } }; (function () { this.$setMaxTokenCount = function (e) { i = e | 0 }, this.$applyToken = function (e) { var t = this.splitRegex.exec(e).slice(1), n = this.token.apply(this, t); if (typeof n == "string") return [{ type: n, value: e }]; var r = []; for (var i = 0, s = n.length; i < s; i++)t[i] && (r[r.length] = { type: n[i], value: t[i] }); return r }, this.$arrayTokens = function (e) { if (!e) return []; var t = this.splitRegex.exec(e); if (!t) return "text"; var n = [], r = this.tokenArray; for (var i = 0, s = r.length; i < s; i++)t[i + 1] && (n[n.length] = { type: r[i], value: t[i + 1] }); return n }, this.removeCapturingGroups = function (e) { var t = e.replace(/\\.|\[(?:\\.|[^\\\]])*|\(\?[:=!]|(\()/g, function (e, t) { return t ? "(?:" : e }); return t }, this.createSplitterRegexp = function (e, t) { if (e.indexOf("(?=") != -1) { var n = 0, r = !1, i = {}; e.replace(/(\\.)|(\((?:\?[=!])?)|(\))|([\[\]])/g, function (e, t, s, o, u, a) { return r ? r = u != "]" : u ? r = !0 : o ? (n == i.stack && (i.end = a + 1, i.stack = -1), n--) : s && (n++, s.length != 1 && (i.stack = n, i.start = a)), e }), i.end != null && /^\)*$/.test(e.substr(i.end)) && (e = e.substring(0, i.start) + e.substr(i.end)) } return e.charAt(0) != "^" && (e = "^" + e), e.charAt(e.length - 1) != "$" && (e += "$"), new RegExp(e, (t || "").replace("g", "")) }, this.getLineTokens = function (e, t) { if (t && typeof t != "string") { var n = t.slice(0); t = n[0], t === "#tmp" && (n.shift(), t = n.shift()) } else var n = []; var r = t || "start", s = this.states[r]; s || (r = "start", s = this.states[r]); var o = this.matchMappings[r], u = this.regExps[r]; u.lastIndex = 0; var a, f = [], l = 0, c = 0, h = { type: null, value: "" }; while (a = u.exec(e)) { var p = o.defaultToken, d = null, v = a[0], m = u.lastIndex; if (m - v.length > l) { var g = e.substring(l, m - v.length); h.type == p ? h.value += g : (h.type && f.push(h), h = { type: p, value: g }) } for (var y = 0; y < a.length - 2; y++) { if (a[y + 1] === undefined) continue; d = s[o[y]], d.onMatch ? p = d.onMatch(v, r, n, e) : p = d.token, d.next && (typeof d.next == "string" ? r = d.next : r = d.next(r, n), s = this.states[r], s || (this.reportError("state doesn't exist", r), r = "start", s = this.states[r]), o = this.matchMappings[r], l = m, u = this.regExps[r], u.lastIndex = m), d.consumeLineEnd && (l = m); break } if (v) if (typeof p == "string") !!d && d.merge === !1 || h.type !== p ? (h.type && f.push(h), h = { type: p, value: v }) : h.value += v; else if (p) { h.type && f.push(h), h = { type: null, value: "" }; for (var y = 0; y < p.length; y++)f.push(p[y]) } if (l == e.length) break; l = m; if (c++ > i) { c > 2 * e.length && this.reportError("infinite loop with in ace tokenizer", { startState: t, line: e }); while (l < e.length) h.type && f.push(h), h = { value: e.substring(l, l += 500), type: "overflow" }; r = "start", n = []; break } } return h.type && f.push(h), n.length > 1 && n[0] !== r && n.unshift("#tmp", r), { tokens: f, state: n.length ? n : r } }, this.reportError = r.reportError }).call(s.prototype), t.Tokenizer = s }), define("ace/mode/text_highlight_rules", ["require", "exports", "module", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../lib/lang"), i = function () { this.$rules = { start: [{ token: "empty_line", regex: "^$" }, { defaultToken: "text" }] } }; (function () { this.addRules = function (e, t) { if (!t) { for (var n in e) this.$rules[n] = e[n]; return } for (var n in e) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i]; if (s.next || s.onMatch) typeof s.next == "string" && s.next.indexOf(t) !== 0 && (s.next = t + s.next), s.nextState && s.nextState.indexOf(t) !== 0 && (s.nextState = t + s.nextState) } this.$rules[t + n] = r } }, this.getRules = function () { return this.$rules }, this.embedRules = function (e, t, n, i, s) { var o = typeof e == "function" ? (new e).getRules() : e; if (i) for (var u = 0; u < i.length; u++)i[u] = t + i[u]; else { i = []; for (var a in o) i.push(t + a) } this.addRules(o, t); if (n) { var f = Array.prototype[s ? "push" : "unshift"]; for (var u = 0; u < i.length; u++)f.apply(this.$rules[i[u]], r.deepCopy(n)) } this.$embeds || (this.$embeds = []), this.$embeds.push(t) }, this.getEmbeds = function () { return this.$embeds }; var e = function (e, t) { return (e != "start" || t.length) && t.unshift(this.nextState, e), this.nextState }, t = function (e, t) { return t.shift(), t.shift() || "start" }; this.normalizeRules = function () { function i(s) { var o = r[s]; o.processed = !0; for (var u = 0; u < o.length; u++) { var a = o[u], f = null; Array.isArray(a) && (f = a, a = {}), !a.regex && a.start && (a.regex = a.start, a.next || (a.next = []), a.next.push({ defaultToken: a.token }, { token: a.token + ".end", regex: a.end || a.start, next: "pop" }), a.token = a.token + ".start", a.push = !0); var l = a.next || a.push; if (l && Array.isArray(l)) { var c = a.stateName; c || (c = a.token, typeof c != "string" && (c = c[0] || ""), r[c] && (c += n++)), r[c] = l, a.next = c, i(c) } else l == "pop" && (a.next = t); a.push && (a.nextState = a.next || a.push, a.next = e, delete a.push); if (a.rules) for (var h in a.rules) r[h] ? r[h].push && r[h].push.apply(r[h], a.rules[h]) : r[h] = a.rules[h]; var p = typeof a == "string" ? a : a.include; p && (Array.isArray(p) ? f = p.map(function (e) { return r[e] }) : f = r[p]); if (f) { var d = [u, 1].concat(f); a.noEscape && (d = d.filter(function (e) { return !e.next })), o.splice.apply(o, d), u-- } a.keywordMap && (a.token = this.createKeywordMapper(a.keywordMap, a.defaultToken || "text", a.caseInsensitive), delete a.defaultToken) } } var n = 0, r = this.$rules; Object.keys(r).forEach(i, this) }, this.createKeywordMapper = function (e, t, n, r) { var i = Object.create(null); return Object.keys(e).forEach(function (t) { var s = e[t]; n && (s = s.toLowerCase()); var o = s.split(r || "|"); for (var u = o.length; u--;)i[o[u]] = t }), Object.getPrototypeOf(i) && (i.__proto__ = null), this.$keywordList = Object.keys(i), e = null, n ? function (e) { return i[e.toLowerCase()] || t } : function (e) { return i[e] || t } }, this.getKeywords = function () { return this.$keywords } }).call(i.prototype), t.TextHighlightRules = i }), define("ace/mode/behaviour", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function () { this.$behaviours = {} }; (function () { this.add = function (e, t, n) { switch (undefined) { case this.$behaviours: this.$behaviours = {}; case this.$behaviours[e]: this.$behaviours[e] = {} }this.$behaviours[e][t] = n }, this.addBehaviours = function (e) { for (var t in e) for (var n in e[t]) this.add(t, n, e[t][n]) }, this.remove = function (e) { this.$behaviours && this.$behaviours[e] && delete this.$behaviours[e] }, this.inherit = function (e, t) { if (typeof e == "function") var n = (new e).getBehaviours(t); else var n = e.getBehaviours(t); this.addBehaviours(n) }, this.getBehaviours = function (e) { if (!e) return this.$behaviours; var t = {}; for (var n = 0; n < e.length; n++)this.$behaviours[e[n]] && (t[e[n]] = this.$behaviours[e[n]]); return t } }).call(r.prototype), t.Behaviour = r }), define("ace/token_iterator", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = function (e, t, n) { this.$session = e, this.$row = t, this.$rowTokens = e.getTokens(t); var r = e.getTokenAt(t, n); this.$tokenIndex = r ? r.index : -1 }; (function () { this.stepBackward = function () { this.$tokenIndex -= 1; while (this.$tokenIndex < 0) { this.$row -= 1; if (this.$row < 0) return this.$row = 0, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = this.$rowTokens.length - 1 } return this.$rowTokens[this.$tokenIndex] }, this.stepForward = function () { this.$tokenIndex += 1; var e; while (this.$tokenIndex >= this.$rowTokens.length) { this.$row += 1, e || (e = this.$session.getLength()); if (this.$row >= e) return this.$row = e - 1, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = 0 } return this.$rowTokens[this.$tokenIndex] }, this.getCurrentToken = function () { return this.$rowTokens[this.$tokenIndex] }, this.getCurrentTokenRow = function () { return this.$row }, this.getCurrentTokenColumn = function () { var e = this.$rowTokens, t = this.$tokenIndex, n = e[t].start; if (n !== undefined) return n; n = 0; while (t > 0) t -= 1, n += e[t].value.length; return n }, this.getCurrentTokenPosition = function () { return { row: this.$row, column: this.getCurrentTokenColumn() } }, this.getCurrentTokenRange = function () { var e = this.$rowTokens[this.$tokenIndex], t = this.getCurrentTokenColumn(); return new r(this.$row, t, this.$row, t + e.value.length) } }).call(i.prototype), t.TokenIterator = i }), define("ace/mode/behaviour/cstyle", ["require", "exports", "module", "ace/lib/oop", "ace/mode/behaviour", "ace/token_iterator", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("../behaviour").Behaviour, s = e("../../token_iterator").TokenIterator, o = e("../../lib/lang"), u = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator"], a = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator", "comment"], f, l = {}, c = { '"': '"', "'": "'" }, h = function (e) { var t = -1; e.multiSelect && (t = e.selection.index, l.rangeCount != e.multiSelect.rangeCount && (l = { rangeCount: e.multiSelect.rangeCount })); if (l[t]) return f = l[t]; f = l[t] = { autoInsertedBrackets: 0, autoInsertedRow: -1, autoInsertedLineEnd: "", maybeInsertedBrackets: 0, maybeInsertedRow: -1, maybeInsertedLineStart: "", maybeInsertedLineEnd: "" } }, p = function (e, t, n, r) { var i = e.end.row - e.start.row; return { text: n + t + r, selection: [0, e.start.column + 1, i, e.end.column + (i ? 0 : 1)] } }, d = function (e) { this.add("braces", "insertion", function (t, n, r, i, s) { var u = r.getCursorPosition(), a = i.doc.getLine(u.row); if (s == "{") { h(r); var l = r.getSelectionRange(), c = i.doc.getTextRange(l); if (c !== "" && c !== "{" && r.getWrapBehavioursEnabled()) return p(l, c, "{", "}"); if (d.isSaneInsertion(r, i)) return /[\]\}\)]/.test(a[u.column]) || r.inMultiSelectMode || e && e.braces ? (d.recordAutoInsert(r, i, "}"), { text: "{}", selection: [1, 1] }) : (d.recordMaybeInsert(r, i, "{"), { text: "{", selection: [1, 1] }) } else if (s == "}") { h(r); var v = a.substring(u.column, u.column + 1); if (v == "}") { var m = i.$findOpeningBracket("}", { column: u.column + 1, row: u.row }); if (m !== null && d.isAutoInsertedClosing(u, a, s)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } else { if (s == "\n" || s == "\r\n") { h(r); var g = ""; d.isMaybeInsertedClosing(u, a) && (g = o.stringRepeat("}", f.maybeInsertedBrackets), d.clearMaybeInsertedClosing()); var v = a.substring(u.column, u.column + 1); if (v === "}") { var y = i.findMatchingBracket({ row: u.row, column: u.column + 1 }, "}"); if (!y) return null; var b = this.$getIndent(i.getLine(y.row)) } else { if (!g) { d.clearMaybeInsertedClosing(); return } var b = this.$getIndent(a) } var w = b + i.getTabString(); return { text: "\n" + w + "\n" + b + g, selection: [1, w.length, 1, w.length] } } d.clearMaybeInsertedClosing() } }), this.add("braces", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "{") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.end.column, i.end.column + 1); if (u == "}") return i.end.column++, i; f.maybeInsertedBrackets-- } }), this.add("parens", "insertion", function (e, t, n, r, i) { if (i == "(") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "(", ")"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, ")"), { text: "()", selection: [1, 1] } } else if (i == ")") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == ")") { var l = r.$findOpeningBracket(")", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("parens", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "(") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == ")") return i.end.column++, i } }), this.add("brackets", "insertion", function (e, t, n, r, i) { if (i == "[") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "[", "]"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, "]"), { text: "[]", selection: [1, 1] } } else if (i == "]") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == "]") { var l = r.$findOpeningBracket("]", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("brackets", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "[") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == "]") return i.end.column++, i } }), this.add("string_dquotes", "insertion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c; if (i.length == 1 && s[i]) { if (this.lineCommentStart && this.lineCommentStart.indexOf(i) != -1) return; h(n); var o = i, u = n.getSelectionRange(), a = r.doc.getTextRange(u); if (a !== "" && (a.length != 1 || !s[a]) && n.getWrapBehavioursEnabled()) return p(u, a, o, o); if (!a) { var f = n.getCursorPosition(), l = r.doc.getLine(f.row), d = l.substring(f.column - 1, f.column), v = l.substring(f.column, f.column + 1), m = r.getTokenAt(f.row, f.column), g = r.getTokenAt(f.row, f.column + 1); if (d == "\\" && m && /escape/.test(m.type)) return null; var y = m && /string|escape/.test(m.type), b = !g || /string|escape/.test(g.type), w; if (v == o) w = y !== b, w && /string\.end/.test(g.type) && (w = !1); else { if (y && !b) return null; if (y && b) return null; var E = r.$mode.tokenRe; E.lastIndex = 0; var S = E.test(d); E.lastIndex = 0; var x = E.test(d); if (S || x) return null; if (v && !/[\s;,.})\]\\]/.test(v)) return null; var T = l[f.column - 2]; if (!(d != o || T != o && !E.test(T))) return null; w = !0 } return { text: w ? o + o : "", selection: [1, 1] } } } }), this.add("string_dquotes", "deletion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c, o = r.doc.getTextRange(i); if (!i.isMultiLine() && s.hasOwnProperty(o)) { h(n); var u = r.doc.getLine(i.start.row), a = u.substring(i.start.column + 1, i.start.column + 2); if (a == o) return i.end.column++, i } }) }; d.isSaneInsertion = function (e, t) { var n = e.getCursorPosition(), r = new s(t, n.row, n.column); if (!this.$matchTokenType(r.getCurrentToken() || "text", u)) { if (/[)}\]]/.test(e.session.getLine(n.row)[n.column])) return !0; var i = new s(t, n.row, n.column + 1); if (!this.$matchTokenType(i.getCurrentToken() || "text", u)) return !1 } return r.stepForward(), r.getCurrentTokenRow() !== n.row || this.$matchTokenType(r.getCurrentToken() || "text", a) }, d.$matchTokenType = function (e, t) { return t.indexOf(e.type || e) > -1 }, d.recordAutoInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isAutoInsertedClosing(r, i, f.autoInsertedLineEnd[0]) || (f.autoInsertedBrackets = 0), f.autoInsertedRow = r.row, f.autoInsertedLineEnd = n + i.substr(r.column), f.autoInsertedBrackets++ }, d.recordMaybeInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isMaybeInsertedClosing(r, i) || (f.maybeInsertedBrackets = 0), f.maybeInsertedRow = r.row, f.maybeInsertedLineStart = i.substr(0, r.column) + n, f.maybeInsertedLineEnd = i.substr(r.column), f.maybeInsertedBrackets++ }, d.isAutoInsertedClosing = function (e, t, n) { return f.autoInsertedBrackets > 0 && e.row === f.autoInsertedRow && n === f.autoInsertedLineEnd[0] && t.substr(e.column) === f.autoInsertedLineEnd }, d.isMaybeInsertedClosing = function (e, t) { return f.maybeInsertedBrackets > 0 && e.row === f.maybeInsertedRow && t.substr(e.column) === f.maybeInsertedLineEnd && t.substr(0, e.column) == f.maybeInsertedLineStart }, d.popAutoInsertedClosing = function () { f.autoInsertedLineEnd = f.autoInsertedLineEnd.substr(1), f.autoInsertedBrackets-- }, d.clearMaybeInsertedClosing = function () { f && (f.maybeInsertedBrackets = 0, f.maybeInsertedRow = -1) }, r.inherits(d, i), t.CstyleBehaviour = d }), define("ace/unicode", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = [48, 9, 8, 25, 5, 0, 2, 25, 48, 0, 11, 0, 5, 0, 6, 22, 2, 30, 2, 457, 5, 11, 15, 4, 8, 0, 2, 0, 18, 116, 2, 1, 3, 3, 9, 0, 2, 2, 2, 0, 2, 19, 2, 82, 2, 138, 2, 4, 3, 155, 12, 37, 3, 0, 8, 38, 10, 44, 2, 0, 2, 1, 2, 1, 2, 0, 9, 26, 6, 2, 30, 10, 7, 61, 2, 9, 5, 101, 2, 7, 3, 9, 2, 18, 3, 0, 17, 58, 3, 100, 15, 53, 5, 0, 6, 45, 211, 57, 3, 18, 2, 5, 3, 11, 3, 9, 2, 1, 7, 6, 2, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 0, 4, 3, 3, 8, 3, 1, 3, 3, 9, 0, 5, 1, 2, 4, 3, 11, 16, 2, 2, 5, 5, 1, 3, 21, 2, 6, 2, 1, 2, 1, 2, 1, 3, 0, 2, 4, 5, 1, 3, 2, 4, 0, 8, 3, 2, 0, 8, 15, 12, 2, 2, 8, 2, 2, 2, 21, 2, 6, 2, 1, 2, 4, 3, 9, 2, 2, 2, 2, 3, 0, 16, 3, 3, 9, 18, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 3, 8, 3, 1, 3, 2, 9, 1, 5, 1, 2, 4, 3, 9, 2, 0, 17, 1, 2, 5, 4, 2, 2, 3, 4, 1, 2, 0, 2, 1, 4, 1, 4, 2, 4, 11, 5, 4, 4, 2, 2, 3, 3, 0, 7, 0, 15, 9, 18, 2, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 4, 7, 2, 2, 2, 3, 8, 1, 2, 1, 7, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 3, 8, 2, 2, 2, 3, 8, 1, 8, 0, 2, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 15, 4, 7, 2, 2, 2, 3, 10, 0, 9, 3, 3, 9, 11, 5, 3, 1, 2, 17, 4, 23, 2, 8, 2, 0, 3, 6, 4, 0, 5, 5, 2, 0, 2, 7, 19, 1, 14, 57, 6, 14, 2, 9, 40, 1, 2, 0, 3, 1, 2, 0, 3, 0, 7, 3, 2, 6, 2, 2, 2, 0, 2, 0, 3, 1, 2, 12, 2, 2, 3, 4, 2, 0, 2, 5, 3, 9, 3, 1, 35, 0, 24, 1, 7, 9, 12, 0, 2, 0, 2, 0, 5, 9, 2, 35, 5, 19, 2, 5, 5, 7, 2, 35, 10, 0, 58, 73, 7, 77, 3, 37, 11, 42, 2, 0, 4, 328, 2, 3, 3, 6, 2, 0, 2, 3, 3, 40, 2, 3, 3, 32, 2, 3, 3, 6, 2, 0, 2, 3, 3, 14, 2, 56, 2, 3, 3, 66, 5, 0, 33, 15, 17, 84, 13, 619, 3, 16, 2, 25, 6, 74, 22, 12, 2, 6, 12, 20, 12, 19, 13, 12, 2, 2, 2, 1, 13, 51, 3, 29, 4, 0, 5, 1, 3, 9, 34, 2, 3, 9, 7, 87, 9, 42, 6, 69, 11, 28, 4, 11, 5, 11, 11, 39, 3, 4, 12, 43, 5, 25, 7, 10, 38, 27, 5, 62, 2, 28, 3, 10, 7, 9, 14, 0, 89, 75, 5, 9, 18, 8, 13, 42, 4, 11, 71, 55, 9, 9, 4, 48, 83, 2, 2, 30, 14, 230, 23, 280, 3, 5, 3, 37, 3, 5, 3, 7, 2, 0, 2, 0, 2, 0, 2, 30, 3, 52, 2, 6, 2, 0, 4, 2, 2, 6, 4, 3, 3, 5, 5, 12, 6, 2, 2, 6, 67, 1, 20, 0, 29, 0, 14, 0, 17, 4, 60, 12, 5, 0, 4, 11, 18, 0, 5, 0, 3, 9, 2, 0, 4, 4, 7, 0, 2, 0, 2, 0, 2, 3, 2, 10, 3, 3, 6, 4, 5, 0, 53, 1, 2684, 46, 2, 46, 2, 132, 7, 6, 15, 37, 11, 53, 10, 0, 17, 22, 10, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 31, 48, 0, 470, 1, 36, 5, 2, 4, 6, 1, 5, 85, 3, 1, 3, 2, 2, 89, 2, 3, 6, 40, 4, 93, 18, 23, 57, 15, 513, 6581, 75, 20939, 53, 1164, 68, 45, 3, 268, 4, 27, 21, 31, 3, 13, 13, 1, 2, 24, 9, 69, 11, 1, 38, 8, 3, 102, 3, 1, 111, 44, 25, 51, 13, 68, 12, 9, 7, 23, 4, 0, 5, 45, 3, 35, 13, 28, 4, 64, 15, 10, 39, 54, 10, 13, 3, 9, 7, 22, 4, 1, 5, 66, 25, 2, 227, 42, 2, 1, 3, 9, 7, 11171, 13, 22, 5, 48, 8453, 301, 3, 61, 3, 105, 39, 6, 13, 4, 6, 11, 2, 12, 2, 4, 2, 0, 2, 1, 2, 1, 2, 107, 34, 362, 19, 63, 3, 53, 41, 11, 5, 15, 17, 6, 13, 1, 25, 2, 33, 4, 2, 134, 20, 9, 8, 25, 5, 0, 2, 25, 12, 88, 4, 5, 3, 5, 3, 5, 3, 2], i = 0, s = []; for (var o = 0; o < r.length; o += 2)s.push(i += r[o]), r[o + 1] && s.push(45, i += r[o + 1]); t.wordChars = String.fromCharCode.apply(null, s) }), define("ace/mode/text", ["require", "exports", "module", "ace/config", "ace/tokenizer", "ace/mode/text_highlight_rules", "ace/mode/behaviour/cstyle", "ace/unicode", "ace/lib/lang", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; var r = e("../config"), i = e("../tokenizer").Tokenizer, s = e("./text_highlight_rules").TextHighlightRules, o = e("./behaviour/cstyle").CstyleBehaviour, u = e("../unicode"), a = e("../lib/lang"), f = e("../token_iterator").TokenIterator, l = e("../range").Range, c = function () { this.HighlightRules = s }; (function () { this.$defaultBehaviour = new o, this.tokenRe = new RegExp("^[" + u.wordChars + "\\$_]+", "g"), this.nonTokenRe = new RegExp("^(?:[^" + u.wordChars + "\\$_]|\\s])+", "g"), this.getTokenizer = function () { return this.$tokenizer || (this.$highlightRules = this.$highlightRules || new this.HighlightRules(this.$highlightRuleConfig), this.$tokenizer = new i(this.$highlightRules.getRules())), this.$tokenizer }, this.lineCommentStart = "", this.blockComment = "", this.toggleCommentLines = function (e, t, n, r) { function w(e) { for (var t = n; t <= r; t++)e(i.getLine(t), t) } var i = t.doc, s = !0, o = !0, u = Infinity, f = t.getTabSize(), l = !1; if (!this.lineCommentStart) { if (!this.blockComment) return !1; var c = this.blockComment.start, h = this.blockComment.end, p = new RegExp("^(\\s*)(?:" + a.escapeRegExp(c) + ")"), d = new RegExp("(?:" + a.escapeRegExp(h) + ")\\s*$"), v = function (e, t) { if (g(e, t)) return; if (!s || /\S/.test(e)) i.insertInLine({ row: t, column: e.length }, h), i.insertInLine({ row: t, column: u }, c) }, m = function (e, t) { var n; (n = e.match(d)) && i.removeInLine(t, e.length - n[0].length, e.length), (n = e.match(p)) && i.removeInLine(t, n[1].length, n[0].length) }, g = function (e, n) { if (p.test(e)) return !0; var r = t.getTokens(n); for (var i = 0; i < r.length; i++)if (r[i].type === "comment") return !0 } } else { if (Array.isArray(this.lineCommentStart)) var p = this.lineCommentStart.map(a.escapeRegExp).join("|"), c = this.lineCommentStart[0]; else var p = a.escapeRegExp(this.lineCommentStart), c = this.lineCommentStart; p = new RegExp("^(\\s*)(?:" + p + ") ?"), l = t.getUseSoftTabs(); var m = function (e, t) { var n = e.match(p); if (!n) return; var r = n[1].length, s = n[0].length; !b(e, r, s) && n[0][s - 1] == " " && s--, i.removeInLine(t, r, s) }, y = c + " ", v = function (e, t) { if (!s || /\S/.test(e)) b(e, u, u) ? i.insertInLine({ row: t, column: u }, y) : i.insertInLine({ row: t, column: u }, c) }, g = function (e, t) { return p.test(e) }, b = function (e, t, n) { var r = 0; while (t-- && e.charAt(t) == " ") r++; if (r % f != 0) return !1; var r = 0; while (e.charAt(n++) == " ") r++; return f > 2 ? r % f != f - 1 : r % f == 0 } } var E = Infinity; w(function (e, t) { var n = e.search(/\S/); n !== -1 ? (n < u && (u = n), o && !g(e, t) && (o = !1)) : E > e.length && (E = e.length) }), u == Infinity && (u = E, s = !1, o = !1), l && u % f != 0 && (u = Math.floor(u / f) * f), w(o ? m : v) }, this.toggleBlockComment = function (e, t, n, r) { var i = this.blockComment; if (!i) return; !i.start && i[0] && (i = i[0]); var s = new f(t, r.row, r.column), o = s.getCurrentToken(), u = t.selection, a = t.selection.toOrientedRange(), c, h; if (o && /comment/.test(o.type)) { var p, d; while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.start); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; p = new l(m, g, m, g + i.start.length); break } o = s.stepBackward() } var s = new f(t, r.row, r.column), o = s.getCurrentToken(); while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.end); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; d = new l(m, g, m, g + i.end.length); break } o = s.stepForward() } d && t.remove(d), p && (t.remove(p), c = p.start.row, h = -i.start.length) } else h = i.start.length, c = n.start.row, t.insert(n.end, i.end), t.insert(n.start, i.start); a.start.row == c && (a.start.column += h), a.end.row == c && (a.end.column += h), t.selection.fromOrientedRange(a) }, this.getNextLineIndent = function (e, t, n) { return this.$getIndent(t) }, this.checkOutdent = function (e, t, n) { return !1 }, this.autoOutdent = function (e, t, n) { }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] }, this.createWorker = function (e) { return null }, this.createModeDelegates = function (e) { this.$embeds = [], this.$modes = {}; for (var t in e) if (e[t]) { var n = e[t], i = n.prototype.$id, s = r.$modes[i]; s || (r.$modes[i] = s = new n), r.$modes[t] || (r.$modes[t] = s), this.$embeds.push(t), this.$modes[t] = s } var o = ["toggleBlockComment", "toggleCommentLines", "getNextLineIndent", "checkOutdent", "autoOutdent", "transformAction", "getCompletions"]; for (var t = 0; t < o.length; t++)(function (e) { var n = o[t], r = e[n]; e[o[t]] = function () { return this.$delegator(n, arguments, r) } })(this) }, this.$delegator = function (e, t, n) { var r = t[0] || "start"; if (typeof r != "string") { if (Array.isArray(r[2])) { var i = r[2][r[2].length - 1], s = this.$modes[i]; if (s) return s[e].apply(s, [r[1]].concat([].slice.call(t, 1))) } r = r[0] || "start" } for (var o = 0; o < this.$embeds.length; o++) { if (!this.$modes[this.$embeds[o]]) continue; var u = r.split(this.$embeds[o]); if (!u[0] && u[1]) { t[0] = u[1]; var s = this.$modes[this.$embeds[o]]; return s[e].apply(s, t) } } var a = n.apply(this, t); return n ? a : undefined }, this.transformAction = function (e, t, n, r, i) { if (this.$behaviour) { var s = this.$behaviour.getBehaviours(); for (var o in s) if (s[o][t]) { var u = s[o][t].apply(this, arguments); if (u) return u } } }, this.getKeywords = function (e) { if (!this.completionKeywords) { var t = this.$tokenizer.rules, n = []; for (var r in t) { var i = t[r]; for (var s = 0, o = i.length; s < o; s++)if (typeof i[s].token == "string") /keyword|support|storage/.test(i[s].token) && n.push(i[s].regex); else if (typeof i[s].token == "object") for (var u = 0, a = i[s].token.length; u < a; u++)if (/keyword|support|storage/.test(i[s].token[u])) { var r = i[s].regex.match(/\(.+?\)/g)[u]; n.push(r.substr(1, r.length - 2)) } } this.completionKeywords = n } return e ? n.concat(this.$keywordList || []) : this.$keywordList }, this.$createKeywordList = function () { return this.$highlightRules || this.getTokenizer(), this.$keywordList = this.$highlightRules.$keywordList || [] }, this.getCompletions = function (e, t, n, r) { var i = this.$keywordList || this.$createKeywordList(); return i.map(function (e) { return { name: e, value: e, score: 0, meta: "keyword" } }) }, this.$id = "ace/mode/text" }).call(c.prototype), t.Mode = c }), define("ace/apply_delta", ["require", "exports", "module"], function (e, t, n) { "use strict"; function r(e, t) { throw console.log("Invalid Delta:", e), "Invalid Delta: " + t } function i(e, t) { return t.row >= 0 && t.row < e.length && t.column >= 0 && t.column <= e[t.row].length } function s(e, t) { t.action != "insert" && t.action != "remove" && r(t, "delta.action must be 'insert' or 'remove'"), t.lines instanceof Array || r(t, "delta.lines must be an Array"), (!t.start || !t.end) && r(t, "delta.start/end must be an present"); var n = t.start; i(e, t.start) || r(t, "delta.start must be contained in document"); var s = t.end; t.action == "remove" && !i(e, s) && r(t, "delta.end must contained in document for 'remove' actions"); var o = s.row - n.row, u = s.column - (o == 0 ? n.column : 0); (o != t.lines.length - 1 || t.lines[o].length != u) && r(t, "delta.range must match delta lines") } t.applyDelta = function (e, t, n) { var r = t.start.row, i = t.start.column, s = e[r] || ""; switch (t.action) { case "insert": var o = t.lines; if (o.length === 1) e[r] = s.substring(0, i) + t.lines[0] + s.substring(i); else { var u = [r, 1].concat(t.lines); e.splice.apply(e, u), e[r] = s.substring(0, i) + e[r], e[r + t.lines.length - 1] += s.substring(i) } break; case "remove": var a = t.end.column, f = t.end.row; r === f ? e[r] = s.substring(0, i) + s.substring(a) : e.splice(r, f - r + 1, s.substring(0, i) + e[f].substring(a)) } } }), define("ace/anchor", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = t.Anchor = function (e, t, n) { this.$onChange = this.onChange.bind(this), this.attach(e), typeof n == "undefined" ? this.setPosition(t.row, t.column) : this.setPosition(t, n) }; (function () { function e(e, t, n) { var r = n ? e.column <= t.column : e.column < t.column; return e.row < t.row || e.row == t.row && r } function t(t, n, r) { var i = t.action == "insert", s = (i ? 1 : -1) * (t.end.row - t.start.row), o = (i ? 1 : -1) * (t.end.column - t.start.column), u = t.start, a = i ? u : t.end; return e(n, u, r) ? { row: n.row, column: n.column } : e(a, n, !r) ? { row: n.row + s, column: n.column + (n.row == a.row ? o : 0) } : { row: u.row, column: u.column } } r.implement(this, i), this.getPosition = function () { return this.$clipPositionToDocument(this.row, this.column) }, this.getDocument = function () { return this.document }, this.$insertRight = !1, this.onChange = function (e) { if (e.start.row == e.end.row && e.start.row != this.row) return; if (e.start.row > this.row) return; var n = t(e, { row: this.row, column: this.column }, this.$insertRight); this.setPosition(n.row, n.column, !0) }, this.setPosition = function (e, t, n) { var r; n ? r = { row: e, column: t } : r = this.$clipPositionToDocument(e, t); if (this.row == r.row && this.column == r.column) return; var i = { row: this.row, column: this.column }; this.row = r.row, this.column = r.column, this._signal("change", { old: i, value: r }) }, this.detach = function () { this.document.off("change", this.$onChange) }, this.attach = function (e) { this.document = e || this.document, this.document.on("change", this.$onChange) }, this.$clipPositionToDocument = function (e, t) { var n = {}; return e >= this.document.getLength() ? (n.row = Math.max(0, this.document.getLength() - 1), n.column = this.document.getLine(n.row).length) : e < 0 ? (n.row = 0, n.column = 0) : (n.row = e, n.column = Math.min(this.document.getLine(n.row).length, Math.max(0, t))), t < 0 && (n.column = 0), n } }).call(s.prototype) }), define("ace/document", ["require", "exports", "module", "ace/lib/oop", "ace/apply_delta", "ace/lib/event_emitter", "ace/range", "ace/anchor"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./apply_delta").applyDelta, s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = e("./anchor").Anchor, a = function (e) { this.$lines = [""], e.length === 0 ? this.$lines = [""] : Array.isArray(e) ? this.insertMergedLines({ row: 0, column: 0 }, e) : this.insert({ row: 0, column: 0 }, e) }; (function () { r.implement(this, s), this.setValue = function (e) { var t = this.getLength() - 1; this.remove(new o(0, 0, t, this.getLine(t).length)), this.insert({ row: 0, column: 0 }, e) }, this.getValue = function () { return this.getAllLines().join(this.getNewLineCharacter()) }, this.createAnchor = function (e, t) { return new u(this, e, t) }, "aaa".split(/a/).length === 0 ? this.$split = function (e) { return e.replace(/\r\n|\r/g, "\n").split("\n") } : this.$split = function (e) { return e.split(/\r\n|\r|\n/) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r\n|\r|\n)/m); this.$autoNewLine = t ? t[1] : "\n", this._signal("changeNewLineMode") }, this.getNewLineCharacter = function () { switch (this.$newLineMode) { case "windows": return "\r\n"; case "unix": return "\n"; default: return this.$autoNewLine || "\n" } }, this.$autoNewLine = "", this.$newLineMode = "auto", this.setNewLineMode = function (e) { if (this.$newLineMode === e) return; this.$newLineMode = e, this._signal("changeNewLineMode") }, this.getNewLineMode = function () { return this.$newLineMode }, this.isNewLine = function (e) { return e == "\r\n" || e == "\r" || e == "\n" }, this.getLine = function (e) { return this.$lines[e] || "" }, this.getLines = function (e, t) { return this.$lines.slice(e, t + 1) }, this.getAllLines = function () { return this.getLines(0, this.getLength()) }, this.getLength = function () { return this.$lines.length }, this.getTextRange = function (e) { return this.getLinesForRange(e).join(this.getNewLineCharacter()) }, this.getLinesForRange = function (e) { var t; if (e.start.row === e.end.row) t = [this.getLine(e.start.row).substring(e.start.column, e.end.column)]; else { t = this.getLines(e.start.row, e.end.row), t[0] = (t[0] || "").substring(e.start.column); var n = t.length - 1; e.end.row - e.start.row == n && (t[n] = t[n].substring(0, e.end.column)) } return t }, this.insertLines = function (e, t) { return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."), this.insertFullLines(e, t) }, this.removeLines = function (e, t) { return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."), this.removeFullLines(e, t) }, this.insertNewLine = function (e) { return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."), this.insertMergedLines(e, ["", ""]) }, this.insert = function (e, t) { return this.getLength() <= 1 && this.$detectNewLine(t), this.insertMergedLines(e, this.$split(t)) }, this.insertInLine = function (e, t) { var n = this.clippedPos(e.row, e.column), r = this.pos(e.row, e.column + t.length); return this.applyDelta({ start: n, end: r, action: "insert", lines: [t] }, !0), this.clonePos(r) }, this.clippedPos = function (e, t) { var n = this.getLength(); e === undefined ? e = n : e < 0 ? e = 0 : e >= n && (e = n - 1, t = undefined); var r = this.getLine(e); return t == undefined && (t = r.length), t = Math.min(Math.max(t, 0), r.length), { row: e, column: t } }, this.clonePos = function (e) { return { row: e.row, column: e.column } }, this.pos = function (e, t) { return { row: e, column: t } }, this.$clipPosition = function (e) { var t = this.getLength(); return e.row >= t ? (e.row = Math.max(0, t - 1), e.column = this.getLine(t - 1).length) : (e.row = Math.max(0, e.row), e.column = Math.min(Math.max(e.column, 0), this.getLine(e.row).length)), e }, this.insertFullLines = function (e, t) { e = Math.min(Math.max(e, 0), this.getLength()); var n = 0; e < this.getLength() ? (t = t.concat([""]), n = 0) : (t = [""].concat(t), e--, n = this.$lines[e].length), this.insertMergedLines({ row: e, column: n }, t) }, this.insertMergedLines = function (e, t) { var n = this.clippedPos(e.row, e.column), r = { row: n.row + t.length - 1, column: (t.length == 1 ? n.column : 0) + t[t.length - 1].length }; return this.applyDelta({ start: n, end: r, action: "insert", lines: t }), this.clonePos(r) }, this.remove = function (e) { var t = this.clippedPos(e.start.row, e.start.column), n = this.clippedPos(e.end.row, e.end.column); return this.applyDelta({ start: t, end: n, action: "remove", lines: this.getLinesForRange({ start: t, end: n }) }), this.clonePos(t) }, this.removeInLine = function (e, t, n) { var r = this.clippedPos(e, t), i = this.clippedPos(e, n); return this.applyDelta({ start: r, end: i, action: "remove", lines: this.getLinesForRange({ start: r, end: i }) }, !0), this.clonePos(r) }, this.removeFullLines = function (e, t) { e = Math.min(Math.max(0, e), this.getLength() - 1), t = Math.min(Math.max(0, t), this.getLength() - 1); var n = t == this.getLength() - 1 && e > 0, r = t < this.getLength() - 1, i = n ? e - 1 : e, s = n ? this.getLine(i).length : 0, u = r ? t + 1 : t, a = r ? 0 : this.getLine(u).length, f = new o(i, s, u, a), l = this.$lines.slice(e, t + 1); return this.applyDelta({ start: f.start, end: f.end, action: "remove", lines: this.getLinesForRange(f) }), l }, this.removeNewLine = function (e) { e < this.getLength() - 1 && e >= 0 && this.applyDelta({ start: this.pos(e, this.getLine(e).length), end: this.pos(e + 1, 0), action: "remove", lines: ["", ""] }) }, this.replace = function (e, t) { e instanceof o || (e = o.fromPoints(e.start, e.end)); if (t.length === 0 && e.isEmpty()) return e.start; if (t == this.getTextRange(e)) return e.end; this.remove(e); var n; return t ? n = this.insert(e.start, t) : n = e.start, n }, this.applyDeltas = function (e) { for (var t = 0; t < e.length; t++)this.applyDelta(e[t]) }, this.revertDeltas = function (e) { for (var t = e.length - 1; t >= 0; t--)this.revertDelta(e[t]) }, this.applyDelta = function (e, t) { var n = e.action == "insert"; if (n ? e.lines.length <= 1 && !e.lines[0] : !o.comparePoints(e.start, e.end)) return; n && e.lines.length > 2e4 ? this.$splitAndapplyLargeDelta(e, 2e4) : (i(this.$lines, e, t), this._signal("change", e)) }, this.$safeApplyDelta = function (e) { var t = this.$lines.length; (e.action == "remove" && e.start.row < t && e.end.row < t || e.action == "insert" && e.start.row <= t) && this.applyDelta(e) }, this.$splitAndapplyLargeDelta = function (e, t) { var n = e.lines, r = n.length - t + 1, i = e.start.row, s = e.start.column; for (var o = 0, u = 0; o < r; o = u) { u += t - 1; var a = n.slice(o, u); a.push(""), this.applyDelta({ start: this.pos(i + o, s), end: this.pos(i + u, s = 0), action: e.action, lines: a }, !0) } e.lines = n.slice(o), e.start.row = i + o, e.start.column = s, this.applyDelta(e, !0) }, this.revertDelta = function (e) { this.$safeApplyDelta({ start: this.clonePos(e.start), end: this.clonePos(e.end), action: e.action == "insert" ? "remove" : "insert", lines: e.lines.slice() }) }, this.indexToPosition = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length; for (var i = t || 0, s = n.length; i < s; i++) { e -= n[i].length + r; if (e < 0) return { row: i, column: e + n[i].length + r } } return { row: s - 1, column: e + n[s - 1].length + r } }, this.positionToIndex = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length, i = 0, s = Math.min(e.row, n.length); for (var o = t || 0; o < s; ++o)i += n[o].length + r; return i + e.column } }).call(a.prototype), t.Document = a }), define("ace/background_tokenizer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = function (e, t) { this.running = !1, this.lines = [], this.states = [], this.currentLine = 0, this.tokenizer = e; var n = this; this.$worker = function () { if (!n.running) return; var e = new Date, t = n.currentLine, r = -1, i = n.doc, s = t; while (n.lines[t]) t++; var o = i.getLength(), u = 0; n.running = !1; while (t < o) { n.$tokenizeRow(t), r = t; do t++; while (n.lines[t]); u++; if (u % 5 === 0 && new Date - e > 20) { n.running = setTimeout(n.$worker, 20); break } } n.currentLine = t, r == -1 && (r = t), s <= r && n.fireUpdateEvent(s, r) } }; (function () { r.implement(this, i), this.setTokenizer = function (e) { this.tokenizer = e, this.lines = [], this.states = [], this.start(0) }, this.setDocument = function (e) { this.doc = e, this.lines = [], this.states = [], this.stop() }, this.fireUpdateEvent = function (e, t) { var n = { first: e, last: t }; this._signal("update", { data: n }) }, this.start = function (e) { this.currentLine = Math.min(e || 0, this.currentLine, this.doc.getLength()), this.lines.splice(this.currentLine, this.lines.length), this.states.splice(this.currentLine, this.states.length), this.stop(), this.running = setTimeout(this.$worker, 700) }, this.scheduleStart = function () { this.running || (this.running = setTimeout(this.$worker, 700)) }, this.$updateOnChange = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.lines[t] = null; else if (e.action == "remove") this.lines.splice(t, n + 1, null), this.states.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.lines.splice.apply(this.lines, r), this.states.splice.apply(this.states, r) } this.currentLine = Math.min(t, this.currentLine, this.doc.getLength()), this.stop() }, this.stop = function () { this.running && clearTimeout(this.running), this.running = !1 }, this.getTokens = function (e) { return this.lines[e] || this.$tokenizeRow(e) }, this.getState = function (e) { return this.currentLine == e && this.$tokenizeRow(e), this.states[e] || "start" }, this.$tokenizeRow = function (e) { var t = this.doc.getLine(e), n = this.states[e - 1], r = this.tokenizer.getLineTokens(t, n, e); return this.states[e] + "" != r.state + "" ? (this.states[e] = r.state, this.lines[e + 1] = null, this.currentLine > e + 1 && (this.currentLine = e + 1)) : this.currentLine == e && (this.currentLine = e + 1), this.lines[e] = r.tokens } }).call(s.prototype), t.BackgroundTokenizer = s }), define("ace/search_highlight", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function (e, t, n) { this.setRegexp(e), this.clazz = t, this.type = n || "text" }; (function () { this.MAX_RANGES = 500, this.setRegexp = function (e) { if (this.regExp + "" == e + "") return; this.regExp = e, this.cache = [] }, this.update = function (e, t, n, i) { if (!this.regExp) return; var o = i.firstRow, u = i.lastRow; for (var a = o; a <= u; a++) { var f = this.cache[a]; f == null && (f = r.getMatchOffsets(n.getLine(a), this.regExp), f.length > this.MAX_RANGES && (f = f.slice(0, this.MAX_RANGES)), f = f.map(function (e) { return new s(a, e.offset, a, e.offset + e.length) }), this.cache[a] = f.length ? f : ""); for (var l = f.length; l--;)t.drawSingleLineMarker(e, f[l].toScreenRange(n), this.clazz, i) } } }).call(o.prototype), t.SearchHighlight = o }), define("ace/edit_session/fold_line", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { this.foldData = e, Array.isArray(t) ? this.folds = t : t = this.folds = [t]; var n = t[t.length - 1]; this.range = new r(t[0].start.row, t[0].start.column, n.end.row, n.end.column), this.start = this.range.start, this.end = this.range.end, this.folds.forEach(function (e) { e.setFoldLine(this) }, this) } var r = e("../range").Range; (function () { this.shiftRow = function (e) { this.start.row += e, this.end.row += e, this.folds.forEach(function (t) { t.start.row += e, t.end.row += e }) }, this.addFold = function (e) { if (e.sameRow) { if (e.start.row < this.startRow || e.endRow > this.endRow) throw new Error("Can't add a fold to this FoldLine as it has no connection"); this.folds.push(e), this.folds.sort(function (e, t) { return -e.range.compareEnd(t.start.row, t.start.column) }), this.range.compareEnd(e.start.row, e.start.column) > 0 ? (this.end.row = e.end.row, this.end.column = e.end.column) : this.range.compareStart(e.end.row, e.end.column) < 0 && (this.start.row = e.start.row, this.start.column = e.start.column) } else if (e.start.row == this.end.row) this.folds.push(e), this.end.row = e.end.row, this.end.column = e.end.column; else { if (e.end.row != this.start.row) throw new Error("Trying to add fold to FoldRow that doesn't have a matching row"); this.folds.unshift(e), this.start.row = e.start.row, this.start.column = e.start.column } e.foldLine = this }, this.containsRow = function (e) { return e >= this.start.row && e <= this.end.row }, this.walk = function (e, t, n) { var r = 0, i = this.folds, s, o, u, a = !0; t == null && (t = this.end.row, n = this.end.column); for (var f = 0; f < i.length; f++) { s = i[f], o = s.range.compareStart(t, n); if (o == -1) { e(null, t, n, r, a); return } u = e(null, s.start.row, s.start.column, r, a), u = !u && e(s.placeholder, s.start.row, s.start.column, r); if (u || o === 0) return; a = !s.sameRow, r = s.end.column } e(null, t, n, r, a) }, this.getNextFoldTo = function (e, t) { var n, r; for (var i = 0; i < this.folds.length; i++) { n = this.folds[i], r = n.range.compareEnd(e, t); if (r == -1) return { fold: n, kind: "after" }; if (r === 0) return { fold: n, kind: "inside" } } return null }, this.addRemoveChars = function (e, t, n) { var r = this.getNextFoldTo(e, t), i, s; if (r) { i = r.fold; if (r.kind == "inside" && i.start.column != t && i.start.row != e) window.console && window.console.log(e, t, i); else if (i.start.row == e) { s = this.folds; var o = s.indexOf(i); o === 0 && (this.start.column += n); for (o; o < s.length; o++) { i = s[o], i.start.column += n; if (!i.sameRow) return; i.end.column += n } this.end.column += n } } }, this.split = function (e, t) { var n = this.getNextFoldTo(e, t); if (!n || n.kind == "inside") return null; var r = n.fold, s = this.folds, o = this.foldData, u = s.indexOf(r), a = s[u - 1]; this.end.row = a.end.row, this.end.column = a.end.column, s = s.splice(u, s.length - u); var f = new i(o, s); return o.splice(o.indexOf(this) + 1, 0, f), f }, this.merge = function (e) { var t = e.folds; for (var n = 0; n < t.length; n++)this.addFold(t[n]); var r = this.foldData; r.splice(r.indexOf(e), 1) }, this.toString = function () { var e = [this.range.toString() + ": ["]; return this.folds.forEach(function (t) { e.push(" " + t.toString()) }), e.push("]"), e.join("\n") }, this.idxToPosition = function (e) { var t = 0; for (var n = 0; n < this.folds.length; n++) { var r = this.folds[n]; e -= r.start.column - t; if (e < 0) return { row: r.start.row, column: r.start.column + e }; e -= r.placeholder.length; if (e < 0) return r.start; t = r.end.column } return { row: this.end.row, column: this.end.column + e } } }).call(i.prototype), t.FoldLine = i }), define("ace/range_list", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = r.comparePoints, s = function () { this.ranges = [], this.$bias = 1 }; (function () { this.comparePoints = i, this.pointIndex = function (e, t, n) { var r = this.ranges; for (var s = n || 0; s < r.length; s++) { var o = r[s], u = i(e, o.end); if (u > 0) continue; var a = i(e, o.start); return u === 0 ? t && a !== 0 ? -s - 2 : s : a > 0 || a === 0 && !t ? s : -s - 1 } return -s - 1 }, this.add = function (e) { var t = !e.isEmpty(), n = this.pointIndex(e.start, t); n < 0 && (n = -n - 1); var r = this.pointIndex(e.end, t, n); return r < 0 ? r = -r - 1 : r++, this.ranges.splice(n, r - n, e) }, this.addList = function (e) { var t = []; for (var n = e.length; n--;)t.push.apply(t, this.add(e[n])); return t }, this.substractPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges.splice(t, 1) }, this.merge = function () { var e = [], t = this.ranges; t = t.sort(function (e, t) { return i(e.start, t.start) }); var n = t[0], r; for (var s = 1; s < t.length; s++) { r = n, n = t[s]; var o = i(r.end, n.start); if (o < 0) continue; if (o == 0 && !r.isEmpty() && !n.isEmpty()) continue; i(r.end, n.end) < 0 && (r.end.row = n.end.row, r.end.column = n.end.column), t.splice(s, 1), e.push(n), n = r, s-- } return this.ranges = t, e }, this.contains = function (e, t) { return this.pointIndex({ row: e, column: t }) >= 0 }, this.containsPoint = function (e) { return this.pointIndex(e) >= 0 }, this.rangeAtPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges[t] }, this.clipRows = function (e, t) { var n = this.ranges; if (n[0].start.row > t || n[n.length - 1].start.row < e) return []; var r = this.pointIndex({ row: e, column: 0 }); r < 0 && (r = -r - 1); var i = this.pointIndex({ row: t, column: 0 }, r); i < 0 && (i = -i - 1); var s = []; for (var o = r; o < i; o++)s.push(n[o]); return s }, this.removeAll = function () { return this.ranges.splice(0, this.ranges.length) }, this.attach = function (e) { this.session && this.detach(), this.session = e, this.onChange = this.$onChange.bind(this), this.session.on("change", this.onChange) }, this.detach = function () { if (!this.session) return; this.session.removeListener("change", this.onChange), this.session = null }, this.$onChange = function (e) { var t = e.start, n = e.end, r = t.row, i = n.row, s = this.ranges; for (var o = 0, u = s.length; o < u; o++) { var a = s[o]; if (a.end.row >= r) break } if (e.action == "insert") { var f = i - r, l = -t.column + n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > r) break; a.start.row == r && a.start.column >= t.column && (a.start.column == t.column && this.$bias <= 0 || (a.start.column += l, a.start.row += f)); if (a.end.row == r && a.end.column >= t.column) { if (a.end.column == t.column && this.$bias < 0) continue; a.end.column == t.column && l > 0 && o < u - 1 && a.end.column > a.start.column && a.end.column == s[o + 1].start.column && (a.end.column -= l), a.end.column += l, a.end.row += f } } } else { var f = r - i, l = t.column - n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > i) break; if (a.end.row < i && (r < a.end.row || r == a.end.row && t.column < a.end.column)) a.end.row = r, a.end.column = t.column; else if (a.end.row == i) if (a.end.column <= n.column) { if (f || a.end.column > t.column) a.end.column = t.column, a.end.row = t.row } else a.end.column += l, a.end.row += f; else a.end.row > i && (a.end.row += f); if (a.start.row < i && (r < a.start.row || r == a.start.row && t.column < a.start.column)) a.start.row = r, a.start.column = t.column; else if (a.start.row == i) if (a.start.column <= n.column) { if (f || a.start.column > t.column) a.start.column = t.column, a.start.row = t.row } else a.start.column += l, a.start.row += f; else a.start.row > i && (a.start.row += f) } } if (f != 0 && o < u) for (; o < u; o++) { var a = s[o]; a.start.row += f, a.end.row += f } } }).call(s.prototype), t.RangeList = s }), define("ace/edit_session/fold", ["require", "exports", "module", "ace/range_list", "ace/lib/oop"], function (e, t, n) { "use strict"; function o(e, t) { e.row -= t.row, e.row == 0 && (e.column -= t.column) } function u(e, t) { o(e.start, t), o(e.end, t) } function a(e, t) { e.row == 0 && (e.column += t.column), e.row += t.row } function f(e, t) { a(e.start, t), a(e.end, t) } var r = e("../range_list").RangeList, i = e("../lib/oop"), s = t.Fold = function (e, t) { this.foldLine = null, this.placeholder = t, this.range = e, this.start = e.start, this.end = e.end, this.sameRow = e.start.row == e.end.row, this.subFolds = this.ranges = [] }; i.inherits(s, r), function () { this.toString = function () { return '"' + this.placeholder + '" ' + this.range.toString() }, this.setFoldLine = function (e) { this.foldLine = e, this.subFolds.forEach(function (t) { t.setFoldLine(e) }) }, this.clone = function () { var e = this.range.clone(), t = new s(e, this.placeholder); return this.subFolds.forEach(function (e) { t.subFolds.push(e.clone()) }), t.collapseChildren = this.collapseChildren, t }, this.addSubFold = function (e) { if (this.range.isEqual(e)) return; u(e, this.start); var t = e.start.row, n = e.start.column; for (var r = 0, i = -1; r < this.subFolds.length; r++) { i = this.subFolds[r].range.compare(t, n); if (i != 1) break } var s = this.subFolds[r], o = 0; if (i == 0) { if (s.range.containsRange(e)) return s.addSubFold(e); o = 1 } var t = e.range.end.row, n = e.range.end.column; for (var a = r, i = -1; a < this.subFolds.length; a++) { i = this.subFolds[a].range.compare(t, n); if (i != 1) break } i == 0 && a++; var f = this.subFolds.splice(r, a - r, e), l = i == 0 ? f.length - 1 : f.length; for (var c = o; c < l; c++)e.addSubFold(f[c]); return e.setFoldLine(this.foldLine), e }, this.restoreRange = function (e) { return f(e, this.start) } }.call(s.prototype) }), define("ace/edit_session/folding", ["require", "exports", "module", "ace/range", "ace/edit_session/fold_line", "ace/edit_session/fold", "ace/token_iterator"], function (e, t, n) { "use strict"; function u() { this.getFoldAt = function (e, t, n) { var r = this.getFoldLine(e); if (!r) return null; var i = r.folds; for (var s = 0; s < i.length; s++) { var o = i[s].range; if (o.contains(e, t)) { if (n == 1 && o.isEnd(e, t) && !o.isEmpty()) continue; if (n == -1 && o.isStart(e, t) && !o.isEmpty()) continue; return i[s] } } }, this.getFoldsInRange = function (e) { var t = e.start, n = e.end, r = this.$foldData, i = []; t.column += 1, n.column -= 1; for (var s = 0; s < r.length; s++) { var o = r[s].range.compareRange(e); if (o == 2) continue; if (o == -2) break; var u = r[s].folds; for (var a = 0; a < u.length; a++) { var f = u[a]; o = f.range.compareRange(e); if (o == -2) break; if (o == 2) continue; if (o == 42) break; i.push(f) } } return t.column -= 1, n.column += 1, i }, this.getFoldsInRangeList = function (e) { if (Array.isArray(e)) { var t = []; e.forEach(function (e) { t = t.concat(this.getFoldsInRange(e)) }, this) } else var t = this.getFoldsInRange(e); return t }, this.getAllFolds = function () { var e = [], t = this.$foldData; for (var n = 0; n < t.length; n++)for (var r = 0; r < t[n].folds.length; r++)e.push(t[n].folds[r]); return e }, this.getFoldStringAt = function (e, t, n, r) { r = r || this.getFoldLine(e); if (!r) return null; var i = { end: { column: 0 } }, s, o; for (var u = 0; u < r.folds.length; u++) { o = r.folds[u]; var a = o.range.compareEnd(e, t); if (a == -1) { s = this.getLine(o.start.row).substring(i.end.column, o.start.column); break } if (a === 0) return null; i = o } return s || (s = this.getLine(o.start.row).substring(i.end.column)), n == -1 ? s.substring(0, t - i.end.column) : n == 1 ? s.substring(t - i.end.column) : s }, this.getFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.start.row <= e && i.end.row >= e) return i; if (i.end.row > e) return null } return null }, this.getNextFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.end.row >= e) return i } return null }, this.getFoldedRowCount = function (e, t) { var n = this.$foldData, r = t - e + 1; for (var i = 0; i < n.length; i++) { var s = n[i], o = s.end.row, u = s.start.row; if (o >= t) { u < t && (u >= e ? r -= t - u : r = 0); break } o >= e && (u >= e ? r -= o - u : r -= o - e + 1) } return r }, this.$addFoldLine = function (e) { return this.$foldData.push(e), this.$foldData.sort(function (e, t) { return e.start.row - t.start.row }), e }, this.addFold = function (e, t) { var n = this.$foldData, r = !1, o; e instanceof s ? o = e : (o = new s(t, e), o.collapseChildren = t.collapseChildren), this.$clipRangeToDocument(o.range); var u = o.start.row, a = o.start.column, f = o.end.row, l = o.end.column, c = this.getFoldAt(u, a, 1), h = this.getFoldAt(f, l, -1); if (c && h == c) return c.addSubFold(o); c && !c.range.isStart(u, a) && this.removeFold(c), h && !h.range.isEnd(f, l) && this.removeFold(h); var p = this.getFoldsInRange(o.range); p.length > 0 && (this.removeFolds(p), p.forEach(function (e) { o.addSubFold(e) })); for (var d = 0; d < n.length; d++) { var v = n[d]; if (f == v.start.row) { v.addFold(o), r = !0; break } if (u == v.end.row) { v.addFold(o), r = !0; if (!o.sameRow) { var m = n[d + 1]; if (m && m.start.row == f) { v.merge(m); break } } break } if (f <= v.start.row) break } return r || (v = this.$addFoldLine(new i(this.$foldData, o))), this.$useWrapMode ? this.$updateWrapData(v.start.row, v.start.row) : this.$updateRowLengthCache(v.start.row, v.start.row), this.$modified = !0, this._signal("changeFold", { data: o, action: "add" }), o }, this.addFolds = function (e) { e.forEach(function (e) { this.addFold(e) }, this) }, this.removeFold = function (e) { var t = e.foldLine, n = t.start.row, r = t.end.row, i = this.$foldData, s = t.folds; if (s.length == 1) i.splice(i.indexOf(t), 1); else if (t.range.isEnd(e.end.row, e.end.column)) s.pop(), t.end.row = s[s.length - 1].end.row, t.end.column = s[s.length - 1].end.column; else if (t.range.isStart(e.start.row, e.start.column)) s.shift(), t.start.row = s[0].start.row, t.start.column = s[0].start.column; else if (e.sameRow) s.splice(s.indexOf(e), 1); else { var o = t.split(e.start.row, e.start.column); s = o.folds, s.shift(), o.start.row = s[0].start.row, o.start.column = s[0].start.column } this.$updating || (this.$useWrapMode ? this.$updateWrapData(n, r) : this.$updateRowLengthCache(n, r)), this.$modified = !0, this._signal("changeFold", { data: e, action: "remove" }) }, this.removeFolds = function (e) { var t = []; for (var n = 0; n < e.length; n++)t.push(e[n]); t.forEach(function (e) { this.removeFold(e) }, this), this.$modified = !0 }, this.expandFold = function (e) { this.removeFold(e), e.subFolds.forEach(function (t) { e.restoreRange(t), this.addFold(t) }, this), e.collapseChildren > 0 && this.foldAll(e.start.row + 1, e.end.row, e.collapseChildren - 1), e.subFolds = [] }, this.expandFolds = function (e) { e.forEach(function (e) { this.expandFold(e) }, this) }, this.unfold = function (e, t) { var n, i; e == null ? (n = new r(0, 0, this.getLength(), 0), t = !0) : typeof e == "number" ? n = new r(e, 0, e, this.getLine(e).length) : "row" in e ? n = r.fromPoints(e, e) : n = e, i = this.getFoldsInRangeList(n); if (t) this.removeFolds(i); else { var s = i; while (s.length) this.expandFolds(s), s = this.getFoldsInRangeList(n) } if (i.length) return i }, this.isRowFolded = function (e, t) { return !!this.getFoldLine(e, t) }, this.getRowFoldEnd = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.end.row : e }, this.getRowFoldStart = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.start.row : e }, this.getFoldDisplayLine = function (e, t, n, r, i) { r == null && (r = e.start.row), i == null && (i = 0), t == null && (t = e.end.row), n == null && (n = this.getLine(t).length); var s = this.doc, o = ""; return e.walk(function (e, t, n, u) { if (t < r) return; if (t == r) { if (n < i) return; u = Math.max(i, u) } e != null ? o += e : o += s.getLine(t).substring(u, n) }, t, n), o }, this.getDisplayLine = function (e, t, n, r) { var i = this.getFoldLine(e); if (!i) { var s; return s = this.doc.getLine(e), s.substring(r || 0, t || s.length) } return this.getFoldDisplayLine(i, e, t, n, r) }, this.$cloneFoldData = function () { var e = []; return e = this.$foldData.map(function (t) { var n = t.folds.map(function (e) { return e.clone() }); return new i(e, n) }), e }, this.toggleFold = function (e) { var t = this.selection, n = t.getRange(), r, i; if (n.isEmpty()) { var s = n.start; r = this.getFoldAt(s.row, s.column); if (r) { this.expandFold(r); return } (i = this.findMatchingBracket(s)) ? n.comparePoint(i) == 1 ? n.end = i : (n.start = i, n.start.column++, n.end.column--) : (i = this.findMatchingBracket({ row: s.row, column: s.column + 1 })) ? (n.comparePoint(i) == 1 ? n.end = i : n.start = i, n.start.column++) : n = this.getCommentFoldRange(s.row, s.column) || n } else { var o = this.getFoldsInRange(n); if (e && o.length) { this.expandFolds(o); return } o.length == 1 && (r = o[0]) } r || (r = this.getFoldAt(n.start.row, n.start.column)); if (r && r.range.toString() == n.toString()) { this.expandFold(r); return } var u = "..."; if (!n.isMultiLine()) { u = this.getTextRange(n); if (u.length < 4) return; u = u.trim().substring(0, 2) + ".." } this.addFold(u, n) }, this.getCommentFoldRange = function (e, t, n) { var i = new o(this, e, t), s = i.getCurrentToken(), u = s.type; if (s && /^comment|string/.test(u)) { u = u.match(/comment|string/)[0], u == "comment" && (u += "|doc-start"); var a = new RegExp(u), f = new r; if (n != 1) { do s = i.stepBackward(); while (s && a.test(s.type)); i.stepForward() } f.start.row = i.getCurrentTokenRow(), f.start.column = i.getCurrentTokenColumn() + 2, i = new o(this, e, t); if (n != -1) { var l = -1; do { s = i.stepForward(); if (l == -1) { var c = this.getState(i.$row); a.test(c) || (l = i.$row) } else if (i.$row > l) break } while (s && a.test(s.type)); s = i.stepBackward() } else s = i.getCurrentToken(); return f.end.row = i.getCurrentTokenRow(), f.end.column = i.getCurrentTokenColumn() + s.value.length - 2, f } }, this.foldAll = function (e, t, n) { n == undefined && (n = 1e5); var r = this.foldWidgets; if (!r) return; t = t || this.getLength(), e = e || 0; for (var i = e; i < t; i++) { r[i] == null && (r[i] = this.getFoldWidget(i)); if (r[i] != "start") continue; var s = this.getFoldWidgetRange(i); if (s && s.isMultiLine() && s.end.row <= t && s.start.row >= e) { i = s.end.row; try { var o = this.addFold("...", s); o && (o.collapseChildren = n) } catch (u) { } } } }, this.$foldStyles = { manual: 1, markbegin: 1, markbeginend: 1 }, this.$foldStyle = "markbegin", this.setFoldStyle = function (e) { if (!this.$foldStyles[e]) throw new Error("invalid fold style: " + e + "[" + Object.keys(this.$foldStyles).join(", ") + "]"); if (this.$foldStyle == e) return; this.$foldStyle = e, e == "manual" && this.unfold(); var t = this.$foldMode; this.$setFolding(null), this.$setFolding(t) }, this.$setFolding = function (e) { if (this.$foldMode == e) return; this.$foldMode = e, this.off("change", this.$updateFoldWidgets), this.off("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets), this._signal("changeAnnotation"); if (!e || this.$foldStyle == "manual") { this.foldWidgets = null; return } this.foldWidgets = [], this.getFoldWidget = e.getFoldWidget.bind(e, this, this.$foldStyle), this.getFoldWidgetRange = e.getFoldWidgetRange.bind(e, this, this.$foldStyle), this.$updateFoldWidgets = this.updateFoldWidgets.bind(this), this.$tokenizerUpdateFoldWidgets = this.tokenizerUpdateFoldWidgets.bind(this), this.on("change", this.$updateFoldWidgets), this.on("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets) }, this.getParentFoldRangeData = function (e, t) { var n = this.foldWidgets; if (!n || t && n[e]) return {}; var r = e - 1, i; while (r >= 0) { var s = n[r]; s == null && (s = n[r] = this.getFoldWidget(r)); if (s == "start") { var o = this.getFoldWidgetRange(r); i || (i = o); if (o && o.end.row >= e) break } r-- } return { range: r !== -1 && o, firstRange: i } }, this.onFoldWidgetClick = function (e, t) { t = t.domEvent; var n = { children: t.shiftKey, all: t.ctrlKey || t.metaKey, siblings: t.altKey }, r = this.$toggleFoldWidget(e, n); if (!r) { var i = t.target || t.srcElement; i && /ace_fold-widget/.test(i.className) && (i.className += " ace_invalid") } }, this.$toggleFoldWidget = function (e, t) { if (!this.getFoldWidget) return; var n = this.getFoldWidget(e), r = this.getLine(e), i = n === "end" ? -1 : 1, s = this.getFoldAt(e, i === -1 ? 0 : r.length, i); if (s) return t.children || t.all ? this.removeFold(s) : this.expandFold(s), s; var o = this.getFoldWidgetRange(e, !0); if (o && !o.isMultiLine()) { s = this.getFoldAt(o.start.row, o.start.column, 1); if (s && o.isEqual(s.range)) return this.removeFold(s), s } if (t.siblings) { var u = this.getParentFoldRangeData(e); if (u.range) var a = u.range.start.row + 1, f = u.range.end.row; this.foldAll(a, f, t.all ? 1e4 : 0) } else t.children ? (f = o ? o.end.row : this.getLength(), this.foldAll(e + 1, f, t.all ? 1e4 : 0)) : o && (t.all && (o.collapseChildren = 1e4), this.addFold("...", o)); return o }, this.toggleFoldWidget = function (e) { var t = this.selection.getCursor().row; t = this.getRowFoldStart(t); var n = this.$toggleFoldWidget(t, {}); if (n) return; var r = this.getParentFoldRangeData(t, !0); n = r.range || r.firstRange; if (n) { t = n.start.row; var i = this.getFoldAt(t, this.getLine(t).length, 1); i ? this.removeFold(i) : this.addFold("...", n) } }, this.updateFoldWidgets = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.foldWidgets[t] = null; else if (e.action == "remove") this.foldWidgets.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.foldWidgets.splice.apply(this.foldWidgets, r) } }, this.tokenizerUpdateFoldWidgets = function (e) { var t = e.data; t.first != t.last && this.foldWidgets.length > t.first && this.foldWidgets.splice(t.first, this.foldWidgets.length) } } var r = e("../range").Range, i = e("./fold_line").FoldLine, s = e("./fold").Fold, o = e("../token_iterator").TokenIterator; t.Folding = u }), define("ace/edit_session/bracket_match", ["require", "exports", "module", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; function s() { this.findMatchingBracket = function (e, t) { if (e.column == 0) return null; var n = t || this.getLine(e.row).charAt(e.column - 1); if (n == "") return null; var r = n.match(/([\(\[\{])|([\)\]\}])/); return r ? r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e) : null }, this.getBracketRange = function (e) { var t = this.getLine(e.row), n = !0, r, s = t.charAt(e.column - 1), o = s && s.match(/([\(\[\{])|([\)\]\}])/); o || (s = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, o = s && s.match(/([\(\[\{])|([\)\]\}])/), n = !1); if (!o) return null; if (o[1]) { var u = this.$findClosingBracket(o[1], e); if (!u) return null; r = i.fromPoints(e, u), n || (r.end.column++, r.start.column--), r.cursor = r.end } else { var u = this.$findOpeningBracket(o[2], e); if (!u) return null; r = i.fromPoints(u, e), n || (r.start.column++, r.end.column--), r.cursor = r.start } return r }, this.getMatchingBracketRanges = function (e) { var t = this.getLine(e.row), n = t.charAt(e.column - 1), r = n && n.match(/([\(\[\{])|([\)\]\}])/); r || (n = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, r = n && n.match(/([\(\[\{])|([\)\]\}])/)); if (!r) return null; var s = new i(e.row, e.column - 1, e.row, e.column), o = r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e); if (!o) return [s]; var u = new i(o.row, o.column, o.row, o.column + 1); return [s, u] }, this.$brackets = { ")": "(", "(": ")", "]": "[", "[": "]", "{": "}", "}": "{", "<": ">", ">": "<" }, this.$findOpeningBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("rparen", ".paren").replace(/\b(?:end)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn() - 2, f = u.value; for (; ;) { while (a >= 0) { var l = f.charAt(a); if (l == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else l == e && (s += 1); a -= 1 } do u = o.stepBackward(); while (u && !n.test(u.type)); if (u == null) break; f = u.value, a = f.length - 1 } return null }, this.$findClosingBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("lparen", ".paren").replace(/\b(?:start|begin)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn(); for (; ;) { var f = u.value, l = f.length; while (a < l) { var c = f.charAt(a); if (c == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else c == e && (s += 1); a += 1 } do u = o.stepForward(); while (u && !n.test(u.type)); if (u == null) break; a = 0 } return null } } var r = e("../token_iterator").TokenIterator, i = e("../range").Range; t.BracketMatch = s }), define("ace/edit_session", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/bidihandler", "ace/config", "ace/lib/event_emitter", "ace/selection", "ace/mode/text", "ace/range", "ace/document", "ace/background_tokenizer", "ace/search_highlight", "ace/edit_session/folding", "ace/edit_session/bracket_match"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./bidihandler").BidiHandler, o = e("./config"), u = e("./lib/event_emitter").EventEmitter, a = e("./selection").Selection, f = e("./mode/text").Mode, l = e("./range").Range, c = e("./document").Document, h = e("./background_tokenizer").BackgroundTokenizer, p = e("./search_highlight").SearchHighlight, d = function (e, t) { this.$breakpoints = [], this.$decorations = [], this.$frontMarkers = {}, this.$backMarkers = {}, this.$markerId = 1, this.$undoSelect = !0, this.$foldData = [], this.id = "session" + ++d.$uid, this.$foldData.toString = function () { return this.join("\n") }, this.on("changeFold", this.onChangeFold.bind(this)), this.$onChange = this.onChange.bind(this); if (typeof e != "object" || !e.getLine) e = new c(e); this.setDocument(e), this.selection = new a(this), this.$bidiHandler = new s(this), o.resetOptions(this), this.setMode(t), o._signal("session", this) }; d.$uid = 0, function () { function m(e) { return e < 4352 ? !1 : e >= 4352 && e <= 4447 || e >= 4515 && e <= 4519 || e >= 4602 && e <= 4607 || e >= 9001 && e <= 9002 || e >= 11904 && e <= 11929 || e >= 11931 && e <= 12019 || e >= 12032 && e <= 12245 || e >= 12272 && e <= 12283 || e >= 12288 && e <= 12350 || e >= 12353 && e <= 12438 || e >= 12441 && e <= 12543 || e >= 12549 && e <= 12589 || e >= 12593 && e <= 12686 || e >= 12688 && e <= 12730 || e >= 12736 && e <= 12771 || e >= 12784 && e <= 12830 || e >= 12832 && e <= 12871 || e >= 12880 && e <= 13054 || e >= 13056 && e <= 19903 || e >= 19968 && e <= 42124 || e >= 42128 && e <= 42182 || e >= 43360 && e <= 43388 || e >= 44032 && e <= 55203 || e >= 55216 && e <= 55238 || e >= 55243 && e <= 55291 || e >= 63744 && e <= 64255 || e >= 65040 && e <= 65049 || e >= 65072 && e <= 65106 || e >= 65108 && e <= 65126 || e >= 65128 && e <= 65131 || e >= 65281 && e <= 65376 || e >= 65504 && e <= 65510 } r.implement(this, u), this.setDocument = function (e) { this.doc && this.doc.removeListener("change", this.$onChange), this.doc = e, e.on("change", this.$onChange), this.bgTokenizer && this.bgTokenizer.setDocument(this.getDocument()), this.resetCaches() }, this.getDocument = function () { return this.doc }, this.$resetRowCache = function (e) { if (!e) { this.$docRowCache = [], this.$screenRowCache = []; return } var t = this.$docRowCache.length, n = this.$getRowCacheIndex(this.$docRowCache, e) + 1; t > n && (this.$docRowCache.splice(n, t), this.$screenRowCache.splice(n, t)) }, this.$getRowCacheIndex = function (e, t) { var n = 0, r = e.length - 1; while (n <= r) { var i = n + r >> 1, s = e[i]; if (t > s) n = i + 1; else { if (!(t < s)) return i; r = i - 1 } } return n - 1 }, this.resetCaches = function () { this.$modified = !0, this.$wrapData = [], this.$rowLengthCache = [], this.$resetRowCache(0), this.bgTokenizer && this.bgTokenizer.start(0) }, this.onChangeFold = function (e) { var t = e.data; this.$resetRowCache(t.start.row) }, this.onChange = function (e) { this.$modified = !0, this.$bidiHandler.onChange(e), this.$resetRowCache(e.start.row); var t = this.$updateInternalDataOnChange(e); !this.$fromUndo && this.$undoManager && (t && t.length && (this.$undoManager.add({ action: "removeFolds", folds: t }, this.mergeUndoDeltas), this.mergeUndoDeltas = !0), this.$undoManager.add(e, this.mergeUndoDeltas), this.mergeUndoDeltas = !0, this.$informUndoManager.schedule()), this.bgTokenizer && this.bgTokenizer.$updateOnChange(e), this._signal("change", e) }, this.setValue = function (e) { this.doc.setValue(e), this.selection.moveTo(0, 0), this.$resetRowCache(0), this.setUndoManager(this.$undoManager), this.getUndoManager().reset() }, this.getValue = this.toString = function () { return this.doc.getValue() }, this.getSelection = function () { return this.selection }, this.getState = function (e) { return this.bgTokenizer.getState(e) }, this.getTokens = function (e) { return this.bgTokenizer.getTokens(e) }, this.getTokenAt = function (e, t) { var n = this.bgTokenizer.getTokens(e), r, i = 0; if (t == null) { var s = n.length - 1; i = this.getLine(e).length } else for (var s = 0; s < n.length; s++) { i += n[s].value.length; if (i >= t) break } return r = n[s], r ? (r.index = s, r.start = i - r.value.length, r) : null }, this.setUndoManager = function (e) { this.$undoManager = e, this.$informUndoManager && this.$informUndoManager.cancel(); if (e) { var t = this; e.addSession(this), this.$syncInformUndoManager = function () { t.$informUndoManager.cancel(), t.mergeUndoDeltas = !1 }, this.$informUndoManager = i.delayedCall(this.$syncInformUndoManager) } else this.$syncInformUndoManager = function () { } }, this.markUndoGroup = function () { this.$syncInformUndoManager && this.$syncInformUndoManager() }, this.$defaultUndoManager = { undo: function () { }, redo: function () { }, hasUndo: function () { }, hasRedo: function () { }, reset: function () { }, add: function () { }, addSelection: function () { }, startNewGroup: function () { }, addSession: function () { } }, this.getUndoManager = function () { return this.$undoManager || this.$defaultUndoManager }, this.getTabString = function () { return this.getUseSoftTabs() ? i.stringRepeat(" ", this.getTabSize()) : " " }, this.setUseSoftTabs = function (e) { this.setOption("useSoftTabs", e) }, this.getUseSoftTabs = function () { return this.$useSoftTabs && !this.$mode.$indentWithTabs }, this.setTabSize = function (e) { this.setOption("tabSize", e) }, this.getTabSize = function () { return this.$tabSize }, this.isTabStop = function (e) { return this.$useSoftTabs && e.column % this.$tabSize === 0 }, this.setNavigateWithinSoftTabs = function (e) { this.setOption("navigateWithinSoftTabs", e) }, this.getNavigateWithinSoftTabs = function () { return this.$navigateWithinSoftTabs }, this.$overwrite = !1, this.setOverwrite = function (e) { this.setOption("overwrite", e) }, this.getOverwrite = function () { return this.$overwrite }, this.toggleOverwrite = function () { this.setOverwrite(!this.$overwrite) }, this.addGutterDecoration = function (e, t) { this.$decorations[e] || (this.$decorations[e] = ""), this.$decorations[e] += " " + t, this._signal("changeBreakpoint", {}) }, this.removeGutterDecoration = function (e, t) { this.$decorations[e] = (this.$decorations[e] || "").replace(" " + t, ""), this._signal("changeBreakpoint", {}) }, this.getBreakpoints = function () { return this.$breakpoints }, this.setBreakpoints = function (e) { this.$breakpoints = []; for (var t = 0; t < e.length; t++)this.$breakpoints[e[t]] = "ace_breakpoint"; this._signal("changeBreakpoint", {}) }, this.clearBreakpoints = function () { this.$breakpoints = [], this._signal("changeBreakpoint", {}) }, this.setBreakpoint = function (e, t) { t === undefined && (t = "ace_breakpoint"), t ? this.$breakpoints[e] = t : delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.clearBreakpoint = function (e) { delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.addMarker = function (e, t, n, r) { var i = this.$markerId++, s = { range: e, type: n || "line", renderer: typeof n == "function" ? n : null, clazz: t, inFront: !!r, id: i }; return r ? (this.$frontMarkers[i] = s, this._signal("changeFrontMarker")) : (this.$backMarkers[i] = s, this._signal("changeBackMarker")), i }, this.addDynamicMarker = function (e, t) { if (!e.update) return; var n = this.$markerId++; return e.id = n, e.inFront = !!t, t ? (this.$frontMarkers[n] = e, this._signal("changeFrontMarker")) : (this.$backMarkers[n] = e, this._signal("changeBackMarker")), e }, this.removeMarker = function (e) { var t = this.$frontMarkers[e] || this.$backMarkers[e]; if (!t) return; var n = t.inFront ? this.$frontMarkers : this.$backMarkers; delete n[e], this._signal(t.inFront ? "changeFrontMarker" : "changeBackMarker") }, this.getMarkers = function (e) { return e ? this.$frontMarkers : this.$backMarkers }, this.highlight = function (e) { if (!this.$searchHighlight) { var t = new p(null, "ace_selected-word", "text"); this.$searchHighlight = this.addDynamicMarker(t) } this.$searchHighlight.setRegexp(e) }, this.highlightLines = function (e, t, n, r) { typeof t != "number" && (n = t, t = e), n || (n = "ace_step"); var i = new l(e, 0, t, Infinity); return i.id = this.addMarker(i, n, "fullLine", r), i }, this.setAnnotations = function (e) { this.$annotations = e, this._signal("changeAnnotation", {}) }, this.getAnnotations = function () { return this.$annotations || [] }, this.clearAnnotations = function () { this.setAnnotations([]) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r?\n)/m); t ? this.$autoNewLine = t[1] : this.$autoNewLine = "\n" }, this.getWordRange = function (e, t) { var n = this.getLine(e), r = !1; t > 0 && (r = !!n.charAt(t - 1).match(this.tokenRe)), r || (r = !!n.charAt(t).match(this.tokenRe)); if (r) var i = this.tokenRe; else if (/^\s+$/.test(n.slice(t - 1, t + 1))) var i = /\s/; else var i = this.nonTokenRe; var s = t; if (s > 0) { do s--; while (s >= 0 && n.charAt(s).match(i)); s++ } var o = t; while (o < n.length && n.charAt(o).match(i)) o++; return new l(e, s, e, o) }, this.getAWordRange = function (e, t) { var n = this.getWordRange(e, t), r = this.getLine(n.end.row); while (r.charAt(n.end.column).match(/[ \t]/)) n.end.column += 1; return n }, this.setNewLineMode = function (e) { this.doc.setNewLineMode(e) }, this.getNewLineMode = function () { return this.doc.getNewLineMode() }, this.setUseWorker = function (e) { this.setOption("useWorker", e) }, this.getUseWorker = function () { return this.$useWorker }, this.onReloadTokenizer = function (e) { var t = e.data; this.bgTokenizer.start(t.first), this._signal("tokenizerUpdate", e) }, this.$modes = o.$modes, this.$mode = null, this.$modeId = null, this.setMode = function (e, t) { if (e && typeof e == "object") { if (e.getTokenizer) return this.$onChangeMode(e); var n = e, r = n.path } else r = e || "ace/mode/text"; this.$modes["ace/mode/text"] || (this.$modes["ace/mode/text"] = new f); if (this.$modes[r] && !n) { this.$onChangeMode(this.$modes[r]), t && t(); return } this.$modeId = r, o.loadModule(["mode", r], function (e) { if (this.$modeId !== r) return t && t(); this.$modes[r] && !n ? this.$onChangeMode(this.$modes[r]) : e && e.Mode && (e = new e.Mode(n), n || (this.$modes[r] = e, e.$id = r), this.$onChangeMode(e)), t && t() }.bind(this)), this.$mode || this.$onChangeMode(this.$modes["ace/mode/text"], !0) }, this.$onChangeMode = function (e, t) { t || (this.$modeId = e.$id); if (this.$mode === e) return; var n = this.$mode; this.$mode = e, this.$stopWorker(), this.$useWorker && this.$startWorker(); var r = e.getTokenizer(); if (r.on !== undefined) { var i = this.onReloadTokenizer.bind(this); r.on("update", i) } if (!this.bgTokenizer) { this.bgTokenizer = new h(r); var s = this; this.bgTokenizer.on("update", function (e) { s._signal("tokenizerUpdate", e) }) } else this.bgTokenizer.setTokenizer(r); this.bgTokenizer.setDocument(this.getDocument()), this.tokenRe = e.tokenRe, this.nonTokenRe = e.nonTokenRe, t || (e.attachToSession && e.attachToSession(this), this.$options.wrapMethod.set.call(this, this.$wrapMethod), this.$setFolding(e.foldingRules), this.bgTokenizer.start(0), this._emit("changeMode", { oldMode: n, mode: e })) }, this.$stopWorker = function () { this.$worker && (this.$worker.terminate(), this.$worker = null) }, this.$startWorker = function () { try { this.$worker = this.$mode.createWorker(this) } catch (e) { o.warn("Could not load worker", e), this.$worker = null } }, this.getMode = function () { return this.$mode }, this.$scrollTop = 0, this.setScrollTop = function (e) { if (this.$scrollTop === e || isNaN(e)) return; this.$scrollTop = e, this._signal("changeScrollTop", e) }, this.getScrollTop = function () { return this.$scrollTop }, this.$scrollLeft = 0, this.setScrollLeft = function (e) { if (this.$scrollLeft === e || isNaN(e)) return; this.$scrollLeft = e, this._signal("changeScrollLeft", e) }, this.getScrollLeft = function () { return this.$scrollLeft }, this.getScreenWidth = function () { return this.$computeWidth(), this.lineWidgets ? Math.max(this.getLineWidgetMaxWidth(), this.screenWidth) : this.screenWidth }, this.getLineWidgetMaxWidth = function () { if (this.lineWidgetsWidth != null) return this.lineWidgetsWidth; var e = 0; return this.lineWidgets.forEach(function (t) { t && t.screenWidth > e && (e = t.screenWidth) }), this.lineWidgetWidth = e }, this.$computeWidth = function (e) { if (this.$modified || e) { this.$modified = !1; if (this.$useWrapMode) return this.screenWidth = this.$wrapLimit; var t = this.doc.getAllLines(), n = this.$rowLengthCache, r = 0, i = 0, s = this.$foldData[i], o = s ? s.start.row : Infinity, u = t.length; for (var a = 0; a < u; a++) { if (a > o) { a = s.end.row + 1; if (a >= u) break; s = this.$foldData[i++], o = s ? s.start.row : Infinity } n[a] == null && (n[a] = this.$getStringScreenWidth(t[a])[0]), n[a] > r && (r = n[a]) } this.screenWidth = r } }, this.getLine = function (e) { return this.doc.getLine(e) }, this.getLines = function (e, t) { return this.doc.getLines(e, t) }, this.getLength = function () { return this.doc.getLength() }, this.getTextRange = function (e) { return this.doc.getTextRange(e || this.selection.getRange()) }, this.insert = function (e, t) { return this.doc.insert(e, t) }, this.remove = function (e) { return this.doc.remove(e) }, this.removeFullLines = function (e, t) { return this.doc.removeFullLines(e, t) }, this.undoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = e.length - 1; n != -1; n--) { var r = e[n]; r.action == "insert" || r.action == "remove" ? this.doc.revertDelta(r) : r.folds && this.addFolds(r.folds) } !t && this.$undoSelect && (e.selectionBefore ? this.selection.fromJSON(e.selectionBefore) : this.selection.setRange(this.$getUndoSelection(e, !0))), this.$fromUndo = !1 }, this.redoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = 0; n < e.length; n++) { var r = e[n]; (r.action == "insert" || r.action == "remove") && this.doc.$safeApplyDelta(r) } !t && this.$undoSelect && (e.selectionAfter ? this.selection.fromJSON(e.selectionAfter) : this.selection.setRange(this.$getUndoSelection(e, !1))), this.$fromUndo = !1 }, this.setUndoSelect = function (e) { this.$undoSelect = e }, this.$getUndoSelection = function (e, t) { function n(e) { return t ? e.action !== "insert" : e.action === "insert" } var r, i; for (var s = 0; s < e.length; s++) { var o = e[s]; if (!o.start) continue; if (!r) { n(o) ? r = l.fromPoints(o.start, o.end) : r = l.fromPoints(o.start, o.start); continue } n(o) ? (i = o.start, r.compare(i.row, i.column) == -1 && r.setStart(i), i = o.end, r.compare(i.row, i.column) == 1 && r.setEnd(i)) : (i = o.start, r.compare(i.row, i.column) == -1 && (r = l.fromPoints(o.start, o.start))) } return r }, this.replace = function (e, t) { return this.doc.replace(e, t) }, this.moveText = function (e, t, n) { var r = this.getTextRange(e), i = this.getFoldsInRange(e), s = l.fromPoints(t, t); if (!n) { this.remove(e); var o = e.start.row - e.end.row, u = o ? -e.end.column : e.start.column - e.end.column; u && (s.start.row == e.end.row && s.start.column > e.end.column && (s.start.column += u), s.end.row == e.end.row && s.end.column > e.end.column && (s.end.column += u)), o && s.start.row >= e.end.row && (s.start.row += o, s.end.row += o) } s.end = this.insert(s.start, r); if (i.length) { var a = e.start, f = s.start, o = f.row - a.row, u = f.column - a.column; this.addFolds(i.map(function (e) { return e = e.clone(), e.start.row == a.row && (e.start.column += u), e.end.row == a.row && (e.end.column += u), e.start.row += o, e.end.row += o, e })) } return s }, this.indentRows = function (e, t, n) { n = n.replace(/\t/g, this.getTabString()); for (var r = e; r <= t; r++)this.doc.insertInLine({ row: r, column: 0 }, n) }, this.outdentRows = function (e) { var t = e.collapseRows(), n = new l(0, 0, 0, 0), r = this.getTabSize(); for (var i = t.start.row; i <= t.end.row; ++i) { var s = this.getLine(i); n.start.row = i, n.end.row = i; for (var o = 0; o < r; ++o)if (s.charAt(o) != " ") break; o < r && s.charAt(o) == " " ? (n.start.column = o, n.end.column = o + 1) : (n.start.column = 0, n.end.column = o), this.remove(n) } }, this.$moveLines = function (e, t, n) { e = this.getRowFoldStart(e), t = this.getRowFoldEnd(t); if (n < 0) { var r = this.getRowFoldStart(e + n); if (r < 0) return 0; var i = r - e } else if (n > 0) { var r = this.getRowFoldEnd(t + n); if (r > this.doc.getLength() - 1) return 0; var i = r - t } else { e = this.$clipRowToDocument(e), t = this.$clipRowToDocument(t); var i = t - e + 1 } var s = new l(e, 0, t, Number.MAX_VALUE), o = this.getFoldsInRange(s).map(function (e) { return e = e.clone(), e.start.row += i, e.end.row += i, e }), u = n == 0 ? this.doc.getLines(e, t) : this.doc.removeFullLines(e, t); return this.doc.insertFullLines(e + i, u), o.length && this.addFolds(o), i }, this.moveLinesUp = function (e, t) { return this.$moveLines(e, t, -1) }, this.moveLinesDown = function (e, t) { return this.$moveLines(e, t, 1) }, this.duplicateLines = function (e, t) { return this.$moveLines(e, t, 0) }, this.$clipRowToDocument = function (e) { return Math.max(0, Math.min(e, this.doc.getLength() - 1)) }, this.$clipColumnToRow = function (e, t) { return t < 0 ? 0 : Math.min(this.doc.getLine(e).length, t) }, this.$clipPositionToDocument = function (e, t) { t = Math.max(0, t); if (e < 0) e = 0, t = 0; else { var n = this.doc.getLength(); e >= n ? (e = n - 1, t = this.doc.getLine(n - 1).length) : t = Math.min(this.doc.getLine(e).length, t) } return { row: e, column: t } }, this.$clipRangeToDocument = function (e) { e.start.row < 0 ? (e.start.row = 0, e.start.column = 0) : e.start.column = this.$clipColumnToRow(e.start.row, e.start.column); var t = this.doc.getLength() - 1; return e.end.row > t ? (e.end.row = t, e.end.column = this.doc.getLine(t).length) : e.end.column = this.$clipColumnToRow(e.end.row, e.end.column), e }, this.$wrapLimit = 80, this.$useWrapMode = !1, this.$wrapLimitRange = { min: null, max: null }, this.setUseWrapMode = function (e) { if (e != this.$useWrapMode) { this.$useWrapMode = e, this.$modified = !0, this.$resetRowCache(0); if (e) { var t = this.getLength(); this.$wrapData = Array(t), this.$updateWrapData(0, t - 1) } this._signal("changeWrapMode") } }, this.getUseWrapMode = function () { return this.$useWrapMode }, this.setWrapLimitRange = function (e, t) { if (this.$wrapLimitRange.min !== e || this.$wrapLimitRange.max !== t) this.$wrapLimitRange = { min: e, max: t }, this.$modified = !0, this.$bidiHandler.markAsDirty(), this.$useWrapMode && this._signal("changeWrapMode") }, this.adjustWrapLimit = function (e, t) { var n = this.$wrapLimitRange; n.max < 0 && (n = { min: t, max: t }); var r = this.$constrainWrapLimit(e, n.min, n.max); return r != this.$wrapLimit && r > 1 ? (this.$wrapLimit = r, this.$modified = !0, this.$useWrapMode && (this.$updateWrapData(0, this.getLength() - 1), this.$resetRowCache(0), this._signal("changeWrapLimit")), !0) : !1 }, this.$constrainWrapLimit = function (e, t, n) { return t && (e = Math.max(t, e)), n && (e = Math.min(n, e)), e }, this.getWrapLimit = function () { return this.$wrapLimit }, this.setWrapLimit = function (e) { this.setWrapLimitRange(e, e) }, this.getWrapLimitRange = function () { return { min: this.$wrapLimitRange.min, max: this.$wrapLimitRange.max } }, this.$updateInternalDataOnChange = function (e) { var t = this.$useWrapMode, n = e.action, r = e.start, i = e.end, s = r.row, o = i.row, u = o - s, a = null; this.$updating = !0; if (u != 0) if (n === "remove") { this[t ? "$wrapData" : "$rowLengthCache"].splice(s, u); var f = this.$foldData; a = this.getFoldsInRange(e), this.removeFolds(a); var l = this.getFoldLine(i.row), c = 0; if (l) { l.addRemoveChars(i.row, i.column, r.column - i.column), l.shiftRow(-u); var h = this.getFoldLine(s); h && h !== l && (h.merge(l), l = h), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= i.row && l.shiftRow(-u) } o = s } else { var p = Array(u); p.unshift(s, 0); var d = t ? this.$wrapData : this.$rowLengthCache; d.splice.apply(d, p); var f = this.$foldData, l = this.getFoldLine(s), c = 0; if (l) { var v = l.range.compareInside(r.row, r.column); v == 0 ? (l = l.split(r.row, r.column), l && (l.shiftRow(u), l.addRemoveChars(o, 0, i.column - r.column))) : v == -1 && (l.addRemoveChars(s, 0, i.column - r.column), l.shiftRow(u)), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= s && l.shiftRow(u) } } else { u = Math.abs(e.start.column - e.end.column), n === "remove" && (a = this.getFoldsInRange(e), this.removeFolds(a), u = -u); var l = this.getFoldLine(s); l && l.addRemoveChars(s, r.column, u) } return t && this.$wrapData.length != this.doc.getLength() && console.error("doc.getLength() and $wrapData.length have to be the same!"), this.$updating = !1, t ? this.$updateWrapData(s, o) : this.$updateRowLengthCache(s, o), a }, this.$updateRowLengthCache = function (e, t, n) { this.$rowLengthCache[e] = null, this.$rowLengthCache[t] = null }, this.$updateWrapData = function (e, t) { var r = this.doc.getAllLines(), i = this.getTabSize(), o = this.$wrapData, u = this.$wrapLimit, a, f, l = e; t = Math.min(t, r.length - 1); while (l <= t) f = this.getFoldLine(l, f), f ? (a = [], f.walk(function (e, t, i, o) { var u; if (e != null) { u = this.$getDisplayTokens(e, a.length), u[0] = n; for (var f = 1; f < u.length; f++)u[f] = s } else u = this.$getDisplayTokens(r[t].substring(o, i), a.length); a = a.concat(u) }.bind(this), f.end.row, r[f.end.row].length + 1), o[f.start.row] = this.$computeWrapSplits(a, u, i), l = f.end.row + 1) : (a = this.$getDisplayTokens(r[l]), o[l] = this.$computeWrapSplits(a, u, i), l++) }; var e = 1, t = 2, n = 3, s = 4, a = 9, c = 10, d = 11, v = 12; this.$computeWrapSplits = function (e, r, i) { function g() { var t = 0; if (m === 0) return t; if (p) for (var n = 0; n < e.length; n++) { var r = e[n]; if (r == c) t += 1; else { if (r != d) { if (r == v) continue; break } t += i } } return h && p !== !1 && (t += i), Math.min(t, m) } function y(t) { var n = t - f; for (var r = f; r < t; r++) { var i = e[r]; if (i === 12 || i === 2) n -= 1 } o.length || (b = g(), o.indent = b), l += n, o.push(l), f = t } if (e.length == 0) return []; var o = [], u = e.length, f = 0, l = 0, h = this.$wrapAsCode, p = this.$indentedSoftWrap, m = r <= Math.max(2 * i, 8) || p === !1 ? 0 : Math.floor(r / 2), b = 0; while (u - f > r - b) { var w = f + r - b; if (e[w - 1] >= c && e[w] >= c) { y(w); continue } if (e[w] == n || e[w] == s) { for (w; w != f - 1; w--)if (e[w] == n) break; if (w > f) { y(w); continue } w = f + r; for (w; w < e.length; w++)if (e[w] != s) break; if (w == e.length) break; y(w); continue } var E = Math.max(w - (r - (r >> 2)), f - 1); while (w > E && e[w] < n) w--; if (h) { while (w > E && e[w] < n) w--; while (w > E && e[w] == a) w-- } else while (w > E && e[w] < c) w--; if (w > E) { y(++w); continue } w = f + r, e[w] == t && w--, y(w - b) } return o }, this.$getDisplayTokens = function (n, r) { var i = [], s; r = r || 0; for (var o = 0; o < n.length; o++) { var u = n.charCodeAt(o); if (u == 9) { s = this.getScreenTabSize(i.length + r), i.push(d); for (var f = 1; f < s; f++)i.push(v) } else u == 32 ? i.push(c) : u > 39 && u < 48 || u > 57 && u < 64 ? i.push(a) : u >= 4352 && m(u) ? i.push(e, t) : i.push(e) } return i }, this.$getStringScreenWidth = function (e, t, n) { if (t == 0) return [0, 0]; t == null && (t = Infinity), n = n || 0; var r, i; for (i = 0; i < e.length; i++) { r = e.charCodeAt(i), r == 9 ? n += this.getScreenTabSize(n) : r >= 4352 && m(r) ? n += 2 : n += 1; if (n > t) break } return [n, i] }, this.lineWidgets = null, this.getRowLength = function (e) { var t = 1; return this.lineWidgets && (t += this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0), !this.$useWrapMode || !this.$wrapData[e] ? t : this.$wrapData[e].length + t }, this.getRowLineCount = function (e) { return !this.$useWrapMode || !this.$wrapData[e] ? 1 : this.$wrapData[e].length + 1 }, this.getRowWrapIndent = function (e) { if (this.$useWrapMode) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE), n = this.$wrapData[t.row]; return n.length && n[0] < t.column ? n.indent : 0 } return 0 }, this.getScreenLastRowColumn = function (e) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE); return this.documentToScreenColumn(t.row, t.column) }, this.getDocumentLastRowColumn = function (e, t) { var n = this.documentToScreenRow(e, t); return this.getScreenLastRowColumn(n) }, this.getDocumentLastRowColumnPosition = function (e, t) { var n = this.documentToScreenRow(e, t); return this.screenToDocumentPosition(n, Number.MAX_VALUE / 10) }, this.getRowSplitData = function (e) { return this.$useWrapMode ? this.$wrapData[e] : undefined }, this.getScreenTabSize = function (e) { return this.$tabSize - (e % this.$tabSize | 0) }, this.screenToDocumentRow = function (e, t) { return this.screenToDocumentPosition(e, t).row }, this.screenToDocumentColumn = function (e, t) { return this.screenToDocumentPosition(e, t).column }, this.screenToDocumentPosition = function (e, t, n) { if (e < 0) return { row: 0, column: 0 }; var r, i = 0, s = 0, o, u = 0, a = 0, f = this.$screenRowCache, l = this.$getRowCacheIndex(f, e), c = f.length; if (c && l >= 0) var u = f[l], i = this.$docRowCache[l], h = e > f[c - 1]; else var h = !c; var p = this.getLength() - 1, d = this.getNextFoldLine(i), v = d ? d.start.row : Infinity; while (u <= e) { a = this.getRowLength(i); if (u + a > e || i >= p) break; u += a, i++, i > v && (i = d.end.row + 1, d = this.getNextFoldLine(i, d), v = d ? d.start.row : Infinity), h && (this.$docRowCache.push(i), this.$screenRowCache.push(u)) } if (d && d.start.row <= i) r = this.getFoldDisplayLine(d), i = d.start.row; else { if (u + a <= e || i > p) return { row: p, column: this.getLine(p).length }; r = this.getLine(i), d = null } var m = 0, g = Math.floor(e - u); if (this.$useWrapMode) { var y = this.$wrapData[i]; y && (o = y[g], g > 0 && y.length && (m = y.indent, s = y[g - 1] || y[y.length - 1], r = r.substring(s))) } return n !== undefined && this.$bidiHandler.isBidiRow(u + g, i, g) && (t = this.$bidiHandler.offsetToCol(n)), s += this.$getStringScreenWidth(r, t - m)[1], this.$useWrapMode && s >= o && (s = o - 1), d ? d.idxToPosition(s) : { row: i, column: s } }, this.documentToScreenPosition = function (e, t) { if (typeof t == "undefined") var n = this.$clipPositionToDocument(e.row, e.column); else n = this.$clipPositionToDocument(e, t); e = n.row, t = n.column; var r = 0, i = null, s = null; s = this.getFoldAt(e, t, 1), s && (e = s.start.row, t = s.start.column); var o, u = 0, a = this.$docRowCache, f = this.$getRowCacheIndex(a, e), l = a.length; if (l && f >= 0) var u = a[f], r = this.$screenRowCache[f], c = e > a[l - 1]; else var c = !l; var h = this.getNextFoldLine(u), p = h ? h.start.row : Infinity; while (u < e) { if (u >= p) { o = h.end.row + 1; if (o > e) break; h = this.getNextFoldLine(o, h), p = h ? h.start.row : Infinity } else o = u + 1; r += this.getRowLength(u), u = o, c && (this.$docRowCache.push(u), this.$screenRowCache.push(r)) } var d = ""; h && u >= p ? (d = this.getFoldDisplayLine(h, e, t), i = h.start.row) : (d = this.getLine(e).substring(0, t), i = e); var v = 0; if (this.$useWrapMode) { var m = this.$wrapData[i]; if (m) { var g = 0; while (d.length >= m[g]) r++, g++; d = d.substring(m[g - 1] || 0, d.length), v = g > 0 ? m.indent : 0 } } return this.lineWidgets && this.lineWidgets[u] && this.lineWidgets[u].rowsAbove && (r += this.lineWidgets[u].rowsAbove), { row: r, column: v + this.$getStringScreenWidth(d)[0] } }, this.documentToScreenColumn = function (e, t) { return this.documentToScreenPosition(e, t).column }, this.documentToScreenRow = function (e, t) { return this.documentToScreenPosition(e, t).row }, this.getScreenLength = function () { var e = 0, t = null; if (!this.$useWrapMode) { e = this.getLength(); var n = this.$foldData; for (var r = 0; r < n.length; r++)t = n[r], e -= t.end.row - t.start.row } else { var i = this.$wrapData.length, s = 0, r = 0, t = this.$foldData[r++], o = t ? t.start.row : Infinity; while (s < i) { var u = this.$wrapData[s]; e += u ? u.length + 1 : 1, s++, s > o && (s = t.end.row + 1, t = this.$foldData[r++], o = t ? t.start.row : Infinity) } } return this.lineWidgets && (e += this.$getWidgetScreenLength()), e }, this.$setFontMetrics = function (e) { if (!this.$enableVarChar) return; this.$getStringScreenWidth = function (t, n, r) { if (n === 0) return [0, 0]; n || (n = Infinity), r = r || 0; var i, s; for (s = 0; s < t.length; s++) { i = t.charAt(s), i === " " ? r += this.getScreenTabSize(r) : r += e.getCharacterWidth(i); if (r > n) break } return [r, s] } }, this.destroy = function () { this.bgTokenizer && (this.bgTokenizer.setDocument(null), this.bgTokenizer = null), this.$stopWorker(), this.removeAllListeners(), this.selection.detach() }, this.isFullWidth = m }.call(d.prototype), e("./edit_session/folding").Folding.call(d.prototype), e("./edit_session/bracket_match").BracketMatch.call(d.prototype), o.defineOptions(d.prototype, "session", { wrap: { set: function (e) { !e || e == "off" ? e = !1 : e == "free" ? e = !0 : e == "printMargin" ? e = -1 : typeof e == "string" && (e = parseInt(e, 10) || !1); if (this.$wrap == e) return; this.$wrap = e; if (!e) this.setUseWrapMode(!1); else { var t = typeof e == "number" ? e : null; this.setWrapLimitRange(t, t), this.setUseWrapMode(!0) } }, get: function () { return this.getUseWrapMode() ? this.$wrap == -1 ? "printMargin" : this.getWrapLimitRange().min ? this.$wrap : "free" : "off" }, handlesSet: !0 }, wrapMethod: { set: function (e) { e = e == "auto" ? this.$mode.type != "text" : e != "text", e != this.$wrapAsCode && (this.$wrapAsCode = e, this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0))) }, initialValue: "auto" }, indentedSoftWrap: { set: function () { this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0)) }, initialValue: !0 }, firstLineNumber: { set: function () { this._signal("changeBreakpoint") }, initialValue: 1 }, useWorker: { set: function (e) { this.$useWorker = e, this.$stopWorker(), e && this.$startWorker() }, initialValue: !0 }, useSoftTabs: { initialValue: !0 }, tabSize: { set: function (e) { e = parseInt(e), e > 0 && this.$tabSize !== e && (this.$modified = !0, this.$rowLengthCache = [], this.$tabSize = e, this._signal("changeTabSize")) }, initialValue: 4, handlesSet: !0 }, navigateWithinSoftTabs: { initialValue: !1 }, foldStyle: { set: function (e) { this.setFoldStyle(e) }, handlesSet: !0 }, overwrite: { set: function (e) { this._signal("changeOverwrite") }, initialValue: !1 }, newLineMode: { set: function (e) { this.doc.setNewLineMode(e) }, get: function () { return this.doc.getNewLineMode() }, handlesSet: !0 }, mode: { set: function (e) { this.setMode(e) }, get: function () { return this.$modeId }, handlesSet: !0 } }), t.EditSession = d }), define("ace/search", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; function u(e, t) { function n(e) { return /\w/.test(e) || t.regExp ? "\\b" : "" } return n(e[0]) + e + n(e[e.length - 1]) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function () { this.$options = {} }; (function () { this.set = function (e) { return i.mixin(this.$options, e), this }, this.getOptions = function () { return r.copyObject(this.$options) }, this.setOptions = function (e) { this.$options = e }, this.find = function (e) { var t = this.$options, n = this.$matchIterator(e, t); if (!n) return !1; var r = null; return n.forEach(function (e, n, i, o) { return r = new s(e, n, i, o), n == o && t.start && t.start.start && t.skipCurrent != 0 && r.isEqual(t.start) ? (r = null, !1) : !0 }), r }, this.findAll = function (e) { var t = this.$options; if (!t.needle) return []; this.$assembleRegExp(t); var n = t.range, i = n ? e.getLines(n.start.row, n.end.row) : e.doc.getAllLines(), o = [], u = t.re; if (t.$isMultiLine) { var a = u.length, f = i.length - a, l; e: for (var c = u.offset || 0; c <= f; c++) { for (var h = 0; h < a; h++)if (i[c + h].search(u[h]) == -1) continue e; var p = i[c], d = i[c + a - 1], v = p.length - p.match(u[0])[0].length, m = d.match(u[a - 1])[0].length; if (l && l.end.row === c && l.end.column > v) continue; o.push(l = new s(c, v, c + a - 1, m)), a > 2 && (c = c + a - 2) } } else for (var g = 0; g < i.length; g++) { var y = r.getMatchOffsets(i[g], u); for (var h = 0; h < y.length; h++) { var b = y[h]; o.push(new s(g, b.offset, g, b.offset + b.length)) } } if (n) { var w = n.start.column, E = n.start.column, g = 0, h = o.length - 1; while (g < h && o[g].start.column < w && o[g].start.row == n.start.row) g++; while (g < h && o[h].end.column > E && o[h].end.row == n.end.row) h--; o = o.slice(g, h + 1); for (g = 0, h = o.length; g < h; g++)o[g].start.row += n.start.row, o[g].end.row += n.start.row } return o }, this.replace = function (e, t) { var n = this.$options, r = this.$assembleRegExp(n); if (n.$isMultiLine) return t; if (!r) return; var i = r.exec(e); if (!i || i[0].length != e.length) return null; t = e.replace(r, t); if (n.preserveCase) { t = t.split(""); for (var s = Math.min(e.length, e.length); s--;) { var o = e[s]; o && o.toLowerCase() != o ? t[s] = t[s].toUpperCase() : t[s] = t[s].toLowerCase() } t = t.join("") } return t }, this.$assembleRegExp = function (e, t) { if (e.needle instanceof RegExp) return e.re = e.needle; var n = e.needle; if (!e.needle) return e.re = !1; e.regExp || (n = r.escapeRegExp(n)), e.wholeWord && (n = u(n, e)); var i = e.caseSensitive ? "gm" : "gmi"; e.$isMultiLine = !t && /[\n\r]/.test(n); if (e.$isMultiLine) return e.re = this.$assembleMultilineRegExp(n, i); try { var s = new RegExp(n, i) } catch (o) { s = !1 } return e.re = s }, this.$assembleMultilineRegExp = function (e, t) { var n = e.replace(/\r\n|\r|\n/g, "$\n^").split("\n"), r = []; for (var i = 0; i < n.length; i++)try { r.push(new RegExp(n[i], t)) } catch (s) { return !1 } return r }, this.$matchIterator = function (e, t) { var n = this.$assembleRegExp(t); if (!n) return !1; var r = t.backwards == 1, i = t.skipCurrent != 0, s = t.range, o = t.start; o || (o = s ? s[r ? "end" : "start"] : e.selection.getRange()), o.start && (o = o[i != r ? "end" : "start"]); var u = s ? s.start.row : 0, a = s ? s.end.row : e.getLength() - 1; if (r) var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n--; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return; if (t.wrap == 0) return; for (n = a, u = o.row; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return }; else var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n += 1; n <= a; n++)if (c(n, 0, e)) return; if (t.wrap == 0) return; for (n = u, a = o.row; n <= a; n++)if (c(n, 0, e)) return }; if (t.$isMultiLine) var l = n.length, c = function (t, i, s) { var o = r ? t - l + 1 : t; if (o < 0) return; var u = e.getLine(o), a = u.search(n[0]); if (!r && a < i || a === -1) return; for (var f = 1; f < l; f++) { u = e.getLine(o + f); if (u.search(n[f]) == -1) return } var c = u.match(n[l - 1])[0].length; if (r && c > i) return; if (s(o, a, o + l - 1, c)) return !0 }; else if (r) var c = function (t, r, i) { var s = e.getLine(t), o = [], u, a = 0; n.lastIndex = 0; while (u = n.exec(s)) { var f = u[0].length; a = u.index; if (!f) { if (a >= s.length) break; n.lastIndex = a += 1 } if (u.index + f > r) break; o.push(u.index, f) } for (var l = o.length - 1; l >= 0; l -= 2) { var c = o[l - 1], f = o[l]; if (i(t, c, t, c + f)) return !0 } }; else var c = function (t, r, i) { var s = e.getLine(t), o, u; n.lastIndex = r; while (u = n.exec(s)) { var a = u[0].length; o = u.index; if (i(t, o, t, o + a)) return !0; if (!a) { n.lastIndex = o += 1; if (o >= s.length) return !1 } } }; return { forEach: f } } }).call(o.prototype), t.Search = o }), define("ace/keyboard/hash_handler", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e, t) { this.platform = t || (i.isMac ? "mac" : "win"), this.commands = {}, this.commandKeyBinding = {}, this.addCommands(e), this.$singleCommand = !0 } function u(e, t) { o.call(this, e, t), this.$singleCommand = !1 } var r = e("../lib/keys"), i = e("../lib/useragent"), s = r.KEY_MODS; u.prototype = o.prototype, function () { function e(e) { return typeof e == "object" && e.bindKey && e.bindKey.position || (e.isDefault ? -100 : 0) } this.addCommand = function (e) { this.commands[e.name] && this.removeCommand(e), this.commands[e.name] = e, e.bindKey && this._buildKeyHash(e) }, this.removeCommand = function (e, t) { var n = e && (typeof e == "string" ? e : e.name); e = this.commands[n], t || delete this.commands[n]; var r = this.commandKeyBinding; for (var i in r) { var s = r[i]; if (s == e) delete r[i]; else if (Array.isArray(s)) { var o = s.indexOf(e); o != -1 && (s.splice(o, 1), s.length == 1 && (r[i] = s[0])) } } }, this.bindKey = function (e, t, n) { typeof e == "object" && e && (n == undefined && (n = e.position), e = e[this.platform]); if (!e) return; if (typeof t == "function") return this.addCommand({ exec: t, bindKey: e, name: t.name || e }); e.split("|").forEach(function (e) { var r = ""; if (e.indexOf(" ") != -1) { var i = e.split(/\s+/); e = i.pop(), i.forEach(function (e) { var t = this.parseKeys(e), n = s[t.hashId] + t.key; r += (r ? " " : "") + n, this._addCommandToBinding(r, "chainKeys") }, this), r += " " } var o = this.parseKeys(e), u = s[o.hashId] + o.key; this._addCommandToBinding(r + u, t, n) }, this) }, this._addCommandToBinding = function (t, n, r) { var i = this.commandKeyBinding, s; if (!n) delete i[t]; else if (!i[t] || this.$singleCommand) i[t] = n; else { Array.isArray(i[t]) ? (s = i[t].indexOf(n)) != -1 && i[t].splice(s, 1) : i[t] = [i[t]], typeof r != "number" && (r = e(n)); var o = i[t]; for (s = 0; s < o.length; s++) { var u = o[s], a = e(u); if (a > r) break } o.splice(s, 0, n) } }, this.addCommands = function (e) { e && Object.keys(e).forEach(function (t) { var n = e[t]; if (!n) return; if (typeof n == "string") return this.bindKey(n, t); typeof n == "function" && (n = { exec: n }); if (typeof n != "object") return; n.name || (n.name = t), this.addCommand(n) }, this) }, this.removeCommands = function (e) { Object.keys(e).forEach(function (t) { this.removeCommand(e[t]) }, this) }, this.bindKeys = function (e) { Object.keys(e).forEach(function (t) { this.bindKey(t, e[t]) }, this) }, this._buildKeyHash = function (e) { this.bindKey(e.bindKey, e) }, this.parseKeys = function (e) { var t = e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function (e) { return e }), n = t.pop(), i = r[n]; if (r.FUNCTION_KEYS[i]) n = r.FUNCTION_KEYS[i].toLowerCase(); else { if (!t.length) return { key: n, hashId: -1 }; if (t.length == 1 && t[0] == "shift") return { key: n.toUpperCase(), hashId: -1 } } var s = 0; for (var o = t.length; o--;) { var u = r.KEY_MODS[t[o]]; if (u == null) return typeof console != "undefined" && console.error("invalid modifier " + t[o] + " in " + e), !1; s |= u } return { key: n, hashId: s } }, this.findKeyCommand = function (t, n) { var r = s[t] + n; return this.commandKeyBinding[r] }, this.handleKeyboard = function (e, t, n, r) { if (r < 0) return; var i = s[t] + n, o = this.commandKeyBinding[i]; e.$keyChain && (e.$keyChain += " " + i, o = this.commandKeyBinding[e.$keyChain] || o); if (o) if (o == "chainKeys" || o[o.length - 1] == "chainKeys") return e.$keyChain = e.$keyChain || i, { command: "null" }; if (e.$keyChain) if (!!t && t != 4 || n.length != 1) { if (t == -1 || r > 0) e.$keyChain = "" } else e.$keyChain = e.$keyChain.slice(0, -i.length - 1); return { command: o } }, this.getStatusText = function (e, t) { return t.$keyChain || "" } }.call(o.prototype), t.HashHandler = o, t.MultiHashHandler = u }), define("ace/commands/command_manager", ["require", "exports", "module", "ace/lib/oop", "ace/keyboard/hash_handler", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../keyboard/hash_handler").MultiHashHandler, s = e("../lib/event_emitter").EventEmitter, o = function (e, t) { i.call(this, t, e), this.byName = this.commands, this.setDefaultHandler("exec", function (e) { return e.command.exec(e.editor, e.args || {}) }) }; r.inherits(o, i), function () { r.implement(this, s), this.exec = function (e, t, n) { if (Array.isArray(e)) { for (var r = e.length; r--;)if (this.exec(e[r], t, n)) return !0; return !1 } typeof e == "string" && (e = this.commands[e]); if (!e) return !1; if (t && t.$readOnly && !e.readOnly) return !1; if (this.$checkCommandState != 0 && e.isAvailable && !e.isAvailable(t)) return !1; var i = { editor: t, command: e, args: n }; return i.returnValue = this._emit("exec", i), this._signal("afterExec", i), i.returnValue === !1 ? !1 : !0 }, this.toggleRecording = function (e) { if (this.$inReplay) return; return e && e._emit("changeStatus"), this.recording ? (this.macro.pop(), this.off("exec", this.$addCommandToMacro), this.macro.length || (this.macro = this.oldMacro), this.recording = !1) : (this.$addCommandToMacro || (this.$addCommandToMacro = function (e) { this.macro.push([e.command, e.args]) }.bind(this)), this.oldMacro = this.macro, this.macro = [], this.on("exec", this.$addCommandToMacro), this.recording = !0) }, this.replay = function (e) { if (this.$inReplay || !this.macro) return; if (this.recording) return this.toggleRecording(e); try { this.$inReplay = !0, this.macro.forEach(function (t) { typeof t == "string" ? this.exec(t, e) : this.exec(t[0], e, t[1]) }, this) } finally { this.$inReplay = !1 } }, this.trimMacro = function (e) { return e.map(function (e) { return typeof e[0] != "string" && (e[0] = e[0].name), e[1] || (e = e[0]), e }) } }.call(o.prototype), t.CommandManager = o }), define("ace/commands/default_commands", ["require", "exports", "module", "ace/lib/lang", "ace/config", "ace/range"], function (e, t, n) { "use strict"; function o(e, t) { return { win: e, mac: t } } var r = e("../lib/lang"), i = e("../config"), s = e("../range").Range; t.commands = [{ name: "showSettingsMenu", bindKey: o("Ctrl-,", "Command-,"), exec: function (e) { i.loadModule("ace/ext/settings_menu", function (t) { t.init(e), e.showSettingsMenu() }) }, readOnly: !0 }, { name: "goToNextError", bindKey: o("Alt-E", "F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, 1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "goToPreviousError", bindKey: o("Alt-Shift-E", "Shift-F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, -1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "selectall", description: "Select all", bindKey: o("Ctrl-A", "Command-A"), exec: function (e) { e.selectAll() }, readOnly: !0 }, { name: "centerselection", description: "Center selection", bindKey: o(null, "Ctrl-L"), exec: function (e) { e.centerSelection() }, readOnly: !0 }, { name: "gotoline", description: "Go to line...", bindKey: o("Ctrl-L", "Command-L"), exec: function (e, t) { typeof t == "number" && !isNaN(t) && e.gotoLine(t), e.prompt({ $type: "gotoLine" }) }, readOnly: !0 }, { name: "fold", bindKey: o("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"), exec: function (e) { e.session.toggleFold(!1) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "unfold", bindKey: o("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"), exec: function (e) { e.session.toggleFold(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleFoldWidget", bindKey: o("F2", "F2"), exec: function (e) { e.session.toggleFoldWidget() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleParentFoldWidget", bindKey: o("Alt-F2", "Alt-F2"), exec: function (e) { e.session.toggleFoldWidget(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "foldall", description: "Fold all", bindKey: o(null, "Ctrl-Command-Option-0"), exec: function (e) { e.session.foldAll() }, scrollIntoView: "center", readOnly: !0 }, { name: "foldOther", description: "Fold other", bindKey: o("Alt-0", "Command-Option-0"), exec: function (e) { e.session.foldAll(), e.session.unfold(e.selection.getAllRanges()) }, scrollIntoView: "center", readOnly: !0 }, { name: "unfoldall", description: "Unfold all", bindKey: o("Alt-Shift-0", "Command-Option-Shift-0"), exec: function (e) { e.session.unfold() }, scrollIntoView: "center", readOnly: !0 }, { name: "findnext", description: "Find next", bindKey: o("Ctrl-K", "Command-G"), exec: function (e) { e.findNext() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "findprevious", description: "Find previous", bindKey: o("Ctrl-Shift-K", "Command-Shift-G"), exec: function (e) { e.findPrevious() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "selectOrFindNext", description: "Select or find next", bindKey: o("Alt-K", "Ctrl-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findNext() }, readOnly: !0 }, { name: "selectOrFindPrevious", description: "Select or find previous", bindKey: o("Alt-Shift-K", "Ctrl-Shift-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findPrevious() }, readOnly: !0 }, { name: "find", description: "Find", bindKey: o("Ctrl-F", "Command-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e) }) }, readOnly: !0 }, { name: "overwrite", description: "Overwrite", bindKey: "Insert", exec: function (e) { e.toggleOverwrite() }, readOnly: !0 }, { name: "selecttostart", description: "Select to start", bindKey: o("Ctrl-Shift-Home", "Command-Shift-Home|Command-Shift-Up"), exec: function (e) { e.getSelection().selectFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotostart", description: "Go to start", bindKey: o("Ctrl-Home", "Command-Home|Command-Up"), exec: function (e) { e.navigateFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectup", description: "Select up", bindKey: o("Shift-Up", "Shift-Up|Ctrl-Shift-P"), exec: function (e) { e.getSelection().selectUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golineup", description: "Go line up", bindKey: o("Up", "Up|Ctrl-P"), exec: function (e, t) { e.navigateUp(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttoend", description: "Select to end", bindKey: o("Ctrl-Shift-End", "Command-Shift-End|Command-Shift-Down"), exec: function (e) { e.getSelection().selectFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotoend", description: "Go to end", bindKey: o("Ctrl-End", "Command-End|Command-Down"), exec: function (e) { e.navigateFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectdown", description: "Select down", bindKey: o("Shift-Down", "Shift-Down|Ctrl-Shift-N"), exec: function (e) { e.getSelection().selectDown() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golinedown", description: "Go line down", bindKey: o("Down", "Down|Ctrl-N"), exec: function (e, t) { e.navigateDown(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordleft", description: "Select word left", bindKey: o("Ctrl-Shift-Left", "Option-Shift-Left"), exec: function (e) { e.getSelection().selectWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordleft", description: "Go to word left", bindKey: o("Ctrl-Left", "Option-Left"), exec: function (e) { e.navigateWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolinestart", description: "Select to line start", bindKey: o("Alt-Shift-Left", "Command-Shift-Left|Ctrl-Shift-A"), exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolinestart", description: "Go to line start", bindKey: o("Alt-Left|Home", "Command-Left|Home|Ctrl-A"), exec: function (e) { e.navigateLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectleft", description: "Select left", bindKey: o("Shift-Left", "Shift-Left|Ctrl-Shift-B"), exec: function (e) { e.getSelection().selectLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoleft", description: "Go to left", bindKey: o("Left", "Left|Ctrl-B"), exec: function (e, t) { e.navigateLeft(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordright", description: "Select word right", bindKey: o("Ctrl-Shift-Right", "Option-Shift-Right"), exec: function (e) { e.getSelection().selectWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordright", description: "Go to word right", bindKey: o("Ctrl-Right", "Option-Right"), exec: function (e) { e.navigateWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolineend", description: "Select to line end", bindKey: o("Alt-Shift-Right", "Command-Shift-Right|Shift-End|Ctrl-Shift-E"), exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolineend", description: "Go to line end", bindKey: o("Alt-Right|End", "Command-Right|End|Ctrl-E"), exec: function (e) { e.navigateLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectright", description: "Select right", bindKey: o("Shift-Right", "Shift-Right"), exec: function (e) { e.getSelection().selectRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoright", description: "Go to right", bindKey: o("Right", "Right|Ctrl-F"), exec: function (e, t) { e.navigateRight(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectpagedown", description: "Select page down", bindKey: "Shift-PageDown", exec: function (e) { e.selectPageDown() }, readOnly: !0 }, { name: "pagedown", description: "Page down", bindKey: o(null, "Option-PageDown"), exec: function (e) { e.scrollPageDown() }, readOnly: !0 }, { name: "gotopagedown", description: "Go to page down", bindKey: o("PageDown", "PageDown|Ctrl-V"), exec: function (e) { e.gotoPageDown() }, readOnly: !0 }, { name: "selectpageup", description: "Select page up", bindKey: "Shift-PageUp", exec: function (e) { e.selectPageUp() }, readOnly: !0 }, { name: "pageup", description: "Page up", bindKey: o(null, "Option-PageUp"), exec: function (e) { e.scrollPageUp() }, readOnly: !0 }, { name: "gotopageup", description: "Go to page up", bindKey: "PageUp", exec: function (e) { e.gotoPageUp() }, readOnly: !0 }, { name: "scrollup", description: "Scroll up", bindKey: o("Ctrl-Up", null), exec: function (e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "scrolldown", description: "Scroll down", bindKey: o("Ctrl-Down", null), exec: function (e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "selectlinestart", description: "Select line start", bindKey: "Shift-Home", exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectlineend", description: "Select line end", bindKey: "Shift-End", exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "togglerecording", description: "Toggle recording", bindKey: o("Ctrl-Alt-E", "Command-Option-E"), exec: function (e) { e.commands.toggleRecording(e) }, readOnly: !0 }, { name: "replaymacro", description: "Replay macro", bindKey: o("Ctrl-Shift-E", "Command-Shift-E"), exec: function (e) { e.commands.replay(e) }, readOnly: !0 }, { name: "jumptomatching", description: "Jump to matching", bindKey: o("Ctrl-\\|Ctrl-P", "Command-\\"), exec: function (e) { e.jumpToMatching() }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "selecttomatching", description: "Select to matching", bindKey: o("Ctrl-Shift-\\|Ctrl-Shift-P", "Command-Shift-\\"), exec: function (e) { e.jumpToMatching(!0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "expandToMatching", description: "Expand to matching", bindKey: o("Ctrl-Shift-M", "Ctrl-Shift-M"), exec: function (e) { e.jumpToMatching(!0, !0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "passKeysToBrowser", description: "Pass keys to browser", bindKey: o(null, null), exec: function () { }, passEvent: !0, readOnly: !0 }, { name: "copy", description: "Copy", exec: function (e) { }, readOnly: !0 }, { name: "cut", description: "Cut", exec: function (e) { var t = e.$copyWithEmptySelection && e.selection.isEmpty(), n = t ? e.selection.getLineRange() : e.selection.getRange(); e._emit("cut", n), n.isEmpty() || e.session.remove(n), e.clearSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "paste", description: "Paste", exec: function (e, t) { e.$handlePaste(t) }, scrollIntoView: "cursor" }, { name: "removeline", description: "Remove line", bindKey: o("Ctrl-D", "Command-D"), exec: function (e) { e.removeLines() }, scrollIntoView: "cursor", multiSelectAction: "forEachLine" }, { name: "duplicateSelection", description: "Duplicate selection", bindKey: o("Ctrl-Shift-D", "Command-Shift-D"), exec: function (e) { e.duplicateSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "sortlines", description: "Sort lines", bindKey: o("Ctrl-Alt-S", "Command-Alt-S"), exec: function (e) { e.sortLines() }, scrollIntoView: "selection", multiSelectAction: "forEachLine" }, { name: "togglecomment", description: "Toggle comment", bindKey: o("Ctrl-/", "Command-/"), exec: function (e) { e.toggleCommentLines() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "toggleBlockComment", description: "Toggle block comment", bindKey: o("Ctrl-Shift-/", "Command-Shift-/"), exec: function (e) { e.toggleBlockComment() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "modifyNumberUp", description: "Modify number up", bindKey: o("Ctrl-Shift-Up", "Alt-Shift-Up"), exec: function (e) { e.modifyNumber(1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "modifyNumberDown", description: "Modify number down", bindKey: o("Ctrl-Shift-Down", "Alt-Shift-Down"), exec: function (e) { e.modifyNumber(-1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "replace", description: "Replace", bindKey: o("Ctrl-H", "Command-Option-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e, !0) }) } }, { name: "undo", description: "Undo", bindKey: o("Ctrl-Z", "Command-Z"), exec: function (e) { e.undo() } }, { name: "redo", description: "Redo", bindKey: o("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"), exec: function (e) { e.redo() } }, { name: "copylinesup", description: "Copy lines up", bindKey: o("Alt-Shift-Up", "Command-Option-Up"), exec: function (e) { e.copyLinesUp() }, scrollIntoView: "cursor" }, { name: "movelinesup", description: "Move lines up", bindKey: o("Alt-Up", "Option-Up"), exec: function (e) { e.moveLinesUp() }, scrollIntoView: "cursor" }, { name: "copylinesdown", description: "Copy lines down", bindKey: o("Alt-Shift-Down", "Command-Option-Down"), exec: function (e) { e.copyLinesDown() }, scrollIntoView: "cursor" }, { name: "movelinesdown", description: "Move lines down", bindKey: o("Alt-Down", "Option-Down"), exec: function (e) { e.moveLinesDown() }, scrollIntoView: "cursor" }, { name: "del", description: "Delete", bindKey: o("Delete", "Delete|Ctrl-D|Shift-Delete"), exec: function (e) { e.remove("right") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "backspace", description: "Backspace", bindKey: o("Shift-Backspace|Backspace", "Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"), exec: function (e) { e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "cut_or_delete", description: "Cut or delete", bindKey: o("Shift-Delete", null), exec: function (e) { if (!e.selection.isEmpty()) return !1; e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestart", description: "Remove to line start", bindKey: o("Alt-Backspace", "Command-Backspace"), exec: function (e) { e.removeToLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineend", description: "Remove to line end", bindKey: o("Alt-Delete", "Ctrl-K|Command-Delete"), exec: function (e) { e.removeToLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestarthard", description: "Remove to line start hard", bindKey: o("Ctrl-Shift-Backspace", null), exec: function (e) { var t = e.selection.getRange(); t.start.column = 0, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineendhard", description: "Remove to line end hard", bindKey: o("Ctrl-Shift-Delete", null), exec: function (e) { var t = e.selection.getRange(); t.end.column = Number.MAX_VALUE, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordleft", description: "Remove word left", bindKey: o("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"), exec: function (e) { e.removeWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordright", description: "Remove word right", bindKey: o("Ctrl-Delete", "Alt-Delete"), exec: function (e) { e.removeWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "outdent", description: "Outdent", bindKey: o("Shift-Tab", "Shift-Tab"), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "indent", description: "Indent", bindKey: o("Tab", "Tab"), exec: function (e) { e.indent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "blockoutdent", description: "Block outdent", bindKey: o("Ctrl-[", "Ctrl-["), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "blockindent", description: "Block indent", bindKey: o("Ctrl-]", "Ctrl-]"), exec: function (e) { e.blockIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "insertstring", description: "Insert string", exec: function (e, t) { e.insert(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "inserttext", description: "Insert text", exec: function (e, t) { e.insert(r.stringRepeat(t.text || "", t.times || 1)) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "splitline", description: "Split line", bindKey: o(null, "Ctrl-O"), exec: function (e) { e.splitLine() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "transposeletters", description: "Transpose letters", bindKey: o("Alt-Shift-X", "Ctrl-T"), exec: function (e) { e.transposeLetters() }, multiSelectAction: function (e) { e.transposeSelections(1) }, scrollIntoView: "cursor" }, { name: "touppercase", description: "To uppercase", bindKey: o("Ctrl-U", "Ctrl-U"), exec: function (e) { e.toUpperCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "tolowercase", description: "To lowercase", bindKey: o("Ctrl-Shift-U", "Ctrl-Shift-U"), exec: function (e) { e.toLowerCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "autoindent", description: "Auto Indent", bindKey: o(null, null), exec: function (e) { e.autoIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "animate" }, { name: "expandtoline", description: "Expand to line", bindKey: o("Ctrl-Shift-L", "Command-Shift-L"), exec: function (e) { var t = e.selection.getRange(); t.start.column = t.end.column = 0, t.end.row++, e.selection.setRange(t, !1) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "joinlines", description: "Join lines", bindKey: o(null, null), exec: function (e) { var t = e.selection.isBackwards(), n = t ? e.selection.getSelectionLead() : e.selection.getSelectionAnchor(), i = t ? e.selection.getSelectionAnchor() : e.selection.getSelectionLead(), o = e.session.doc.getLine(n.row).length, u = e.session.doc.getTextRange(e.selection.getRange()), a = u.replace(/\n\s*/, " ").length, f = e.session.doc.getLine(n.row); for (var l = n.row + 1; l <= i.row + 1; l++) { var c = r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l))); c.length !== 0 && (c = " " + c), f += c } i.row + 1 < e.session.doc.getLength() - 1 && (f += e.session.doc.getNewLineCharacter()), e.clearSelection(), e.session.doc.replace(new s(n.row, 0, i.row + 2, 0), f), a > 0 ? (e.selection.moveCursorTo(n.row, n.column), e.selection.selectTo(n.row, n.column + a)) : (o = e.session.doc.getLine(n.row).length > o ? o + 1 : o, e.selection.moveCursorTo(n.row, o)) }, multiSelectAction: "forEach", readOnly: !0 }, { name: "invertSelection", description: "Invert selection", bindKey: o(null, null), exec: function (e) { var t = e.session.doc.getLength() - 1, n = e.session.doc.getLine(t).length, r = e.selection.rangeList.ranges, i = []; r.length < 1 && (r = [e.selection.getRange()]); for (var o = 0; o < r.length; o++)o == r.length - 1 && (r[o].end.row !== t || r[o].end.column !== n) && i.push(new s(r[o].end.row, r[o].end.column, t, n)), o === 0 ? (r[o].start.row !== 0 || r[o].start.column !== 0) && i.push(new s(0, 0, r[o].start.row, r[o].start.column)) : i.push(new s(r[o - 1].end.row, r[o - 1].end.column, r[o].start.row, r[o].start.column)); e.exitMultiSelectMode(), e.clearSelection(); for (var o = 0; o < i.length; o++)e.selection.addRange(i[o], !1) }, readOnly: !0, scrollIntoView: "none" }, { name: "addLineAfter", exec: function (e) { e.selection.clearSelection(), e.navigateLineEnd(), e.insert("\n") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "addLineBefore", exec: function (e) { e.selection.clearSelection(); var t = e.getCursorPosition(); e.selection.moveTo(t.row - 1, Number.MAX_VALUE), e.insert("\n"), t.row === 0 && e.navigateUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "openCommandPallete", description: "Open command pallete", bindKey: o("F1", "F1"), exec: function (e) { e.prompt({ $type: "commands" }) }, readOnly: !0 }, { name: "modeSelect", description: "Change language mode...", bindKey: o(null, null), exec: function (e) { e.prompt({ $type: "modes" }) }, readOnly: !0 }] }), define("ace/editor", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/useragent", "ace/keyboard/textinput", "ace/mouse/mouse_handler", "ace/mouse/fold_handler", "ace/keyboard/keybinding", "ace/edit_session", "ace/search", "ace/range", "ace/lib/event_emitter", "ace/commands/command_manager", "ace/commands/default_commands", "ace/config", "ace/token_iterator", "ace/clipboard"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/lang"), o = e("./lib/useragent"), u = e("./keyboard/textinput").TextInput, a = e("./mouse/mouse_handler").MouseHandler, f = e("./mouse/fold_handler").FoldHandler, l = e("./keyboard/keybinding").KeyBinding, c = e("./edit_session").EditSession, h = e("./search").Search, p = e("./range").Range, d = e("./lib/event_emitter").EventEmitter, v = e("./commands/command_manager").CommandManager, m = e("./commands/default_commands").commands, g = e("./config"), y = e("./token_iterator").TokenIterator, b = e("./clipboard"), w = function (e, t, n) { this.$toDestroy = []; var r = e.getContainerElement(); this.container = r, this.renderer = e, this.id = "editor" + ++w.$uid, this.commands = new v(o.isMac ? "mac" : "win", m), typeof document == "object" && (this.textInput = new u(e.getTextAreaContainer(), this), this.renderer.textarea = this.textInput.getElement(), this.$mouseHandler = new a(this), new f(this)), this.keyBinding = new l(this), this.$search = (new h).set({ wrap: !0 }), this.$historyTracker = this.$historyTracker.bind(this), this.commands.on("exec", this.$historyTracker), this.$initOperationListeners(), this._$emitInputEvent = s.delayedCall(function () { this._signal("input", {}), this.session && this.session.bgTokenizer && this.session.bgTokenizer.scheduleStart() }.bind(this)), this.on("change", function (e, t) { t._$emitInputEvent.schedule(31) }), this.setSession(t || n && n.session || new c("")), g.resetOptions(this), n && this.setOptions(n), g._signal("editor", this) }; w.$uid = 0, function () { r.implement(this, d), this.$initOperationListeners = function () { this.commands.on("exec", this.startOperation.bind(this), !0), this.commands.on("afterExec", this.endOperation.bind(this), !0), this.$opResetTimer = s.delayedCall(this.endOperation.bind(this, !0)), this.on("change", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.docChanged = !0 }.bind(this), !0), this.on("changeSelection", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.selectionChanged = !0 }.bind(this), !0) }, this.curOp = null, this.prevOp = {}, this.startOperation = function (e) { if (this.curOp) { if (!e || this.curOp.command) return; this.prevOp = this.curOp } e || (this.previousCommand = null, e = {}), this.$opResetTimer.schedule(), this.curOp = this.session.curOp = { command: e.command || {}, args: e.args, scrollTop: this.renderer.scrollTop }, this.curOp.selectionBefore = this.selection.toJSON() }, this.endOperation = function (e) { if (this.curOp && this.session) { if (e && e.returnValue === !1 || !this.session) return this.curOp = null; if (e == 1 && this.curOp.command && this.curOp.command.name == "mouse") return; this._signal("beforeEndOperation"); if (!this.curOp) return; var t = this.curOp.command, n = t && t.scrollIntoView; if (n) { switch (n) { case "center-animate": n = "animate"; case "center": this.renderer.scrollCursorIntoView(null, .5); break; case "animate": case "cursor": this.renderer.scrollCursorIntoView(); break; case "selectionPart": var r = this.selection.getRange(), i = this.renderer.layerConfig; (r.start.row >= i.lastRow || r.end.row <= i.firstRow) && this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead); break; default: }n == "animate" && this.renderer.animateScrolling(this.curOp.scrollTop) } var s = this.selection.toJSON(); this.curOp.selectionAfter = s, this.$lastSel = this.selection.toJSON(), this.session.getUndoManager().addSelection(s), this.prevOp = this.curOp, this.curOp = null } }, this.$mergeableCommands = ["backspace", "del", "insertstring"], this.$historyTracker = function (e) { if (!this.$mergeUndoDeltas) return; var t = this.prevOp, n = this.$mergeableCommands, r = t.command && e.command.name == t.command.name; if (e.command.name == "insertstring") { var i = e.args; this.mergeNextCommand === undefined && (this.mergeNextCommand = !0), r = r && this.mergeNextCommand && (!/\s/.test(i) || /\s/.test(t.args)), this.mergeNextCommand = !0 } else r = r && n.indexOf(e.command.name) !== -1; this.$mergeUndoDeltas != "always" && Date.now() - this.sequenceStartTime > 2e3 && (r = !1), r ? this.session.mergeUndoDeltas = !0 : n.indexOf(e.command.name) !== -1 && (this.sequenceStartTime = Date.now()) }, this.setKeyboardHandler = function (e, t) { if (e && typeof e == "string" && e != "ace") { this.$keybindingId = e; var n = this; g.loadModule(["keybinding", e], function (r) { n.$keybindingId == e && n.keyBinding.setKeyboardHandler(r && r.handler), t && t() }) } else this.$keybindingId = null, this.keyBinding.setKeyboardHandler(e), t && t() }, this.getKeyboardHandler = function () { return this.keyBinding.getKeyboardHandler() }, this.setSession = function (e) { if (this.session == e) return; this.curOp && this.endOperation(), this.curOp = {}; var t = this.session; if (t) { this.session.off("change", this.$onDocumentChange), this.session.off("changeMode", this.$onChangeMode), this.session.off("tokenizerUpdate", this.$onTokenizerUpdate), this.session.off("changeTabSize", this.$onChangeTabSize), this.session.off("changeWrapLimit", this.$onChangeWrapLimit), this.session.off("changeWrapMode", this.$onChangeWrapMode), this.session.off("changeFold", this.$onChangeFold), this.session.off("changeFrontMarker", this.$onChangeFrontMarker), this.session.off("changeBackMarker", this.$onChangeBackMarker), this.session.off("changeBreakpoint", this.$onChangeBreakpoint), this.session.off("changeAnnotation", this.$onChangeAnnotation), this.session.off("changeOverwrite", this.$onCursorChange), this.session.off("changeScrollTop", this.$onScrollTopChange), this.session.off("changeScrollLeft", this.$onScrollLeftChange); var n = this.session.getSelection(); n.off("changeCursor", this.$onCursorChange), n.off("changeSelection", this.$onSelectionChange) } this.session = e, e ? (this.$onDocumentChange = this.onDocumentChange.bind(this), e.on("change", this.$onDocumentChange), this.renderer.setSession(e), this.$onChangeMode = this.onChangeMode.bind(this), e.on("changeMode", this.$onChangeMode), this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this), e.on("tokenizerUpdate", this.$onTokenizerUpdate), this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer), e.on("changeTabSize", this.$onChangeTabSize), this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this), e.on("changeWrapLimit", this.$onChangeWrapLimit), this.$onChangeWrapMode = this.onChangeWrapMode.bind(this), e.on("changeWrapMode", this.$onChangeWrapMode), this.$onChangeFold = this.onChangeFold.bind(this), e.on("changeFold", this.$onChangeFold), this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this), this.session.on("changeFrontMarker", this.$onChangeFrontMarker), this.$onChangeBackMarker = this.onChangeBackMarker.bind(this), this.session.on("changeBackMarker", this.$onChangeBackMarker), this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this), this.session.on("changeBreakpoint", this.$onChangeBreakpoint), this.$onChangeAnnotation = this.onChangeAnnotation.bind(this), this.session.on("changeAnnotation", this.$onChangeAnnotation), this.$onCursorChange = this.onCursorChange.bind(this), this.session.on("changeOverwrite", this.$onCursorChange), this.$onScrollTopChange = this.onScrollTopChange.bind(this), this.session.on("changeScrollTop", this.$onScrollTopChange), this.$onScrollLeftChange = this.onScrollLeftChange.bind(this), this.session.on("changeScrollLeft", this.$onScrollLeftChange), this.selection = e.getSelection(), this.selection.on("changeCursor", this.$onCursorChange), this.$onSelectionChange = this.onSelectionChange.bind(this), this.selection.on("changeSelection", this.$onSelectionChange), this.onChangeMode(), this.onCursorChange(), this.onScrollTopChange(), this.onScrollLeftChange(), this.onSelectionChange(), this.onChangeFrontMarker(), this.onChangeBackMarker(), this.onChangeBreakpoint(), this.onChangeAnnotation(), this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(), this.renderer.updateFull()) : (this.selection = null, this.renderer.setSession(e)), this._signal("changeSession", { session: e, oldSession: t }), this.curOp = null, t && t._signal("changeEditor", { oldEditor: this }), e && e._signal("changeEditor", { editor: this }), e && e.bgTokenizer && e.bgTokenizer.scheduleStart() }, this.getSession = function () { return this.session }, this.setValue = function (e, t) { return this.session.doc.setValue(e), t ? t == 1 ? this.navigateFileEnd() : t == -1 && this.navigateFileStart() : this.selectAll(), e }, this.getValue = function () { return this.session.getValue() }, this.getSelection = function () { return this.selection }, this.resize = function (e) { this.renderer.onResize(e) }, this.setTheme = function (e, t) { this.renderer.setTheme(e, t) }, this.getTheme = function () { return this.renderer.getTheme() }, this.setStyle = function (e) { this.renderer.setStyle(e) }, this.unsetStyle = function (e) { this.renderer.unsetStyle(e) }, this.getFontSize = function () { return this.getOption("fontSize") || i.computedStyle(this.container).fontSize }, this.setFontSize = function (e) { this.setOption("fontSize", e) }, this.$highlightBrackets = function () { if (this.$highlightPending) return; var e = this; this.$highlightPending = !0, setTimeout(function () { e.$highlightPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; t.$bracketHighlight && (t.$bracketHighlight.markerIds.forEach(function (e) { t.removeMarker(e) }), t.$bracketHighlight = null); var n = t.getMatchingBracketRanges(e.getCursorPosition()); !n && t.$mode.getMatching && (n = t.$mode.getMatching(e.session)); if (!n) return; var r = "ace_bracket"; Array.isArray(n) ? n.length == 1 && (r = "ace_error_bracket") : n = [n], n.length == 2 && (p.comparePoints(n[0].end, n[1].start) == 0 ? n = [p.fromPoints(n[0].start, n[1].end)] : p.comparePoints(n[0].start, n[1].end) == 0 && (n = [p.fromPoints(n[1].start, n[0].end)])), t.$bracketHighlight = { ranges: n, markerIds: n.map(function (e) { return t.addMarker(e, r, "text") }) } }, 50) }, this.$highlightTags = function () { if (this.$highlightTagPending) return; var e = this; this.$highlightTagPending = !0, setTimeout(function () { e.$highlightTagPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; var n = e.getCursorPosition(), r = new y(e.session, n.row, n.column), i = r.getCurrentToken(); if (!i || !/\b(?:tag-open|tag-name)/.test(i.type)) { t.removeMarker(t.$tagHighlight), t.$tagHighlight = null; return } if (i.type.indexOf("tag-open") != -1) { i = r.stepForward(); if (!i) return } var s = i.value, o = 0, u = r.stepBackward(); if (u.value == "<") { do u = i, i = r.stepForward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === "= 0) } else { do i = u, u = r.stepBackward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === " 1) && (t = !1) } if (e.$highlightLineMarker && !t) e.removeMarker(e.$highlightLineMarker.id), e.$highlightLineMarker = null; else if (!e.$highlightLineMarker && t) { var n = new p(t.row, t.column, t.row, Infinity); n.id = e.addMarker(n, "ace_active-line", "screenLine"), e.$highlightLineMarker = n } else t && (e.$highlightLineMarker.start.row = t.row, e.$highlightLineMarker.end.row = t.row, e.$highlightLineMarker.start.column = t.column, e._signal("changeBackMarker")) }, this.onSelectionChange = function (e) { var t = this.session; t.$selectionMarker && t.removeMarker(t.$selectionMarker), t.$selectionMarker = null; if (!this.selection.isEmpty()) { var n = this.selection.getRange(), r = this.getSelectionStyle(); t.$selectionMarker = t.addMarker(n, "ace_selection", r) } else this.$updateHighlightActiveLine(); var i = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp(); this.session.highlight(i), this._signal("changeSelection") }, this.$getSelectionHighLightRegexp = function () { var e = this.session, t = this.getSelectionRange(); if (t.isEmpty() || t.isMultiLine()) return; var n = t.start.column, r = t.end.column, i = e.getLine(t.start.row), s = i.substring(n, r); if (s.length > 5e3 || !/[\w\d]/.test(s)) return; var o = this.$search.$assembleRegExp({ wholeWord: !0, caseSensitive: !0, needle: s }), u = i.substring(n - 1, r + 1); if (!o.test(u)) return; return o }, this.onChangeFrontMarker = function () { this.renderer.updateFrontMarkers() }, this.onChangeBackMarker = function () { this.renderer.updateBackMarkers() }, this.onChangeBreakpoint = function () { this.renderer.updateBreakpoints() }, this.onChangeAnnotation = function () { this.renderer.setAnnotations(this.session.getAnnotations()) }, this.onChangeMode = function (e) { this.renderer.updateText(), this._emit("changeMode", e) }, this.onChangeWrapLimit = function () { this.renderer.updateFull() }, this.onChangeWrapMode = function () { this.renderer.onResize(!0) }, this.onChangeFold = function () { this.$updateHighlightActiveLine(), this.renderer.updateFull() }, this.getSelectedText = function () { return this.session.getTextRange(this.getSelectionRange()) }, this.getCopyText = function () { var e = this.getSelectedText(), t = this.session.doc.getNewLineCharacter(), n = !1; if (!e && this.$copyWithEmptySelection) { n = !0; var r = this.selection.getAllRanges(); for (var i = 0; i < r.length; i++) { var s = r[i]; if (i && r[i - 1].start.row == s.start.row) continue; e += this.session.getLine(s.start.row) + t } } var o = { text: e }; return this._signal("copy", o), b.lineMode = n ? o.text : "", o.text }, this.onCopy = function () { this.commands.exec("copy", this) }, this.onCut = function () { this.commands.exec("cut", this) }, this.onPaste = function (e, t) { var n = { text: e, event: t }; this.commands.exec("paste", this, n) }, this.$handlePaste = function (e) { typeof e == "string" && (e = { text: e }), this._signal("paste", e); var t = e.text, n = t == b.lineMode, r = this.session; if (!this.inMultiSelectMode || this.inVirtualSelectionMode) n ? r.insert({ row: this.selection.lead.row, column: 0 }, t) : this.insert(t); else if (n) this.selection.rangeList.ranges.forEach(function (e) { r.insert({ row: e.start.row, column: 0 }, t) }); else { var i = t.split(/\r\n|\r|\n/), s = this.selection.rangeList.ranges, o = i.length == 2 && (!i[0] || !i[1]); if (i.length != s.length || o) return this.commands.exec("insertstring", this, t); for (var u = s.length; u--;) { var a = s[u]; a.isEmpty() || r.remove(a), r.insert(a.start, i[u]) } } }, this.execCommand = function (e, t) { return this.commands.exec(e, this, t) }, this.insert = function (e, t) { var n = this.session, r = n.getMode(), i = this.getCursorPosition(); if (this.getBehavioursEnabled() && !t) { var s = r.transformAction(n.getState(i.row), "insertion", this, n, e); s && (e !== s.text && (this.inVirtualSelectionMode || (this.session.mergeUndoDeltas = !1, this.mergeNextCommand = !1)), e = s.text) } e == " " && (e = this.session.getTabString()); if (!this.selection.isEmpty()) { var o = this.getSelectionRange(); i = this.session.remove(o), this.clearSelection() } else if (this.session.getOverwrite() && e.indexOf("\n") == -1) { var o = new p.fromPoints(i, i); o.end.column += e.length, this.session.remove(o) } if (e == "\n" || e == "\r\n") { var u = n.getLine(i.row); if (i.column > u.search(/\S|$/)) { var a = u.substr(i.column).search(/\S|$/); n.doc.removeInLine(i.row, i.column, i.column + a) } } this.clearSelection(); var f = i.column, l = n.getState(i.row), u = n.getLine(i.row), c = r.checkOutdent(l, u, e); n.insert(i, e), s && s.selection && (s.selection.length == 2 ? this.selection.setSelectionRange(new p(i.row, f + s.selection[0], i.row, f + s.selection[1])) : this.selection.setSelectionRange(new p(i.row + s.selection[0], s.selection[1], i.row + s.selection[2], s.selection[3]))); if (this.$enableAutoIndent) { if (n.getDocument().isNewLine(e)) { var h = r.getNextLineIndent(l, u.slice(0, i.column), n.getTabString()); n.insert({ row: i.row + 1, column: 0 }, h) } c && r.autoOutdent(l, n, i.row) } }, this.autoIndent = function () { var e = this.session, t = e.getMode(), n, r; if (this.selection.isEmpty()) n = 0, r = e.doc.getLength() - 1; else { var i = this.getSelectionRange(); n = i.start.row, r = i.end.row } var s = "", o = "", u = "", a, f, l, c = e.getTabString(); for (var h = n; h <= r; h++)h > 0 && (s = e.getState(h - 1), o = e.getLine(h - 1), u = t.getNextLineIndent(s, o, c)), a = e.getLine(h), f = t.$getIndent(a), u !== f && (f.length > 0 && (l = new p(h, 0, h, f.length), e.remove(l)), u.length > 0 && e.insert({ row: h, column: 0 }, u)), t.autoOutdent(s, e, h) }, this.onTextInput = function (e, t) { if (!t) return this.keyBinding.onTextInput(e); this.startOperation({ command: { name: "insertstring" } }); var n = this.applyComposition.bind(this, e, t); this.selection.rangeCount ? this.forEachSelection(n) : n(), this.endOperation() }, this.applyComposition = function (e, t) { if (t.extendLeft || t.extendRight) { var n = this.selection.getRange(); n.start.column -= t.extendLeft, n.end.column += t.extendRight, n.start.column < 0 && (n.start.row--, n.start.column += this.session.getLine(n.start.row).length + 1), this.selection.setRange(n), !e && !n.isEmpty() && this.remove() } (e || !this.selection.isEmpty()) && this.insert(e, !0); if (t.restoreStart || t.restoreEnd) { var n = this.selection.getRange(); n.start.column -= t.restoreStart, n.end.column -= t.restoreEnd, this.selection.setRange(n) } }, this.onCommandKey = function (e, t, n) { return this.keyBinding.onCommandKey(e, t, n) }, this.setOverwrite = function (e) { this.session.setOverwrite(e) }, this.getOverwrite = function () { return this.session.getOverwrite() }, this.toggleOverwrite = function () { this.session.toggleOverwrite() }, this.setScrollSpeed = function (e) { this.setOption("scrollSpeed", e) }, this.getScrollSpeed = function () { return this.getOption("scrollSpeed") }, this.setDragDelay = function (e) { this.setOption("dragDelay", e) }, this.getDragDelay = function () { return this.getOption("dragDelay") }, this.setSelectionStyle = function (e) { this.setOption("selectionStyle", e) }, this.getSelectionStyle = function () { return this.getOption("selectionStyle") }, this.setHighlightActiveLine = function (e) { this.setOption("highlightActiveLine", e) }, this.getHighlightActiveLine = function () { return this.getOption("highlightActiveLine") }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.setHighlightSelectedWord = function (e) { this.setOption("highlightSelectedWord", e) }, this.getHighlightSelectedWord = function () { return this.$highlightSelectedWord }, this.setAnimatedScroll = function (e) { this.renderer.setAnimatedScroll(e) }, this.getAnimatedScroll = function () { return this.renderer.getAnimatedScroll() }, this.setShowInvisibles = function (e) { this.renderer.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.renderer.getShowInvisibles() }, this.setDisplayIndentGuides = function (e) { this.renderer.setDisplayIndentGuides(e) }, this.getDisplayIndentGuides = function () { return this.renderer.getDisplayIndentGuides() }, this.setShowPrintMargin = function (e) { this.renderer.setShowPrintMargin(e) }, this.getShowPrintMargin = function () { return this.renderer.getShowPrintMargin() }, this.setPrintMarginColumn = function (e) { this.renderer.setPrintMarginColumn(e) }, this.getPrintMarginColumn = function () { return this.renderer.getPrintMarginColumn() }, this.setReadOnly = function (e) { this.setOption("readOnly", e) }, this.getReadOnly = function () { return this.getOption("readOnly") }, this.setBehavioursEnabled = function (e) { this.setOption("behavioursEnabled", e) }, this.getBehavioursEnabled = function () { return this.getOption("behavioursEnabled") }, this.setWrapBehavioursEnabled = function (e) { this.setOption("wrapBehavioursEnabled", e) }, this.getWrapBehavioursEnabled = function () { return this.getOption("wrapBehavioursEnabled") }, this.setShowFoldWidgets = function (e) { this.setOption("showFoldWidgets", e) }, this.getShowFoldWidgets = function () { return this.getOption("showFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.remove = function (e) { this.selection.isEmpty() && (e == "left" ? this.selection.selectLeft() : this.selection.selectRight()); var t = this.getSelectionRange(); if (this.getBehavioursEnabled()) { var n = this.session, r = n.getState(t.start.row), i = n.getMode().transformAction(r, "deletion", this, n, t); if (t.end.column === 0) { var s = n.getTextRange(t); if (s[s.length - 1] == "\n") { var o = n.getLine(t.end.row); /^\s+$/.test(o) && (t.end.column = o.length) } } i && (t = i) } this.session.remove(t), this.clearSelection() }, this.removeWordRight = function () { this.selection.isEmpty() && this.selection.selectWordRight(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeWordLeft = function () { this.selection.isEmpty() && this.selection.selectWordLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineStart = function () { this.selection.isEmpty() && this.selection.selectLineStart(), this.selection.isEmpty() && this.selection.selectLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineEnd = function () { this.selection.isEmpty() && this.selection.selectLineEnd(); var e = this.getSelectionRange(); e.start.column == e.end.column && e.start.row == e.end.row && (e.end.column = 0, e.end.row++), this.session.remove(e), this.clearSelection() }, this.splitLine = function () { this.selection.isEmpty() || (this.session.remove(this.getSelectionRange()), this.clearSelection()); var e = this.getCursorPosition(); this.insert("\n"), this.moveCursorToPosition(e) }, this.transposeLetters = function () { if (!this.selection.isEmpty()) return; var e = this.getCursorPosition(), t = e.column; if (t === 0) return; var n = this.session.getLine(e.row), r, i; t < n.length ? (r = n.charAt(t) + n.charAt(t - 1), i = new p(e.row, t - 1, e.row, t + 1)) : (r = n.charAt(t - 1) + n.charAt(t - 2), i = new p(e.row, t - 2, e.row, t)), this.session.replace(i, r), this.session.selection.moveToPosition(i.end) }, this.toLowerCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toLowerCase()), this.selection.setSelectionRange(e) }, this.toUpperCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toUpperCase()), this.selection.setSelectionRange(e) }, this.indent = function () { var e = this.session, t = this.getSelectionRange(); if (t.start.row < t.end.row) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } if (t.start.column < t.end.column) { var r = e.getTextRange(t); if (!/^\s+$/.test(r)) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } } var i = e.getLine(t.start.row), o = t.start, u = e.getTabSize(), a = e.documentToScreenColumn(o.row, o.column); if (this.session.getUseSoftTabs()) var f = u - a % u, l = s.stringRepeat(" ", f); else { var f = a % u; while (i[t.start.column - 1] == " " && f) t.start.column--, f--; this.selection.setSelectionRange(t), l = " " } return this.insert(l) }, this.blockIndent = function () { var e = this.$getSelectedRows(); this.session.indentRows(e.first, e.last, " ") }, this.blockOutdent = function () { var e = this.session.getSelection(); this.session.outdentRows(e.getRange()) }, this.sortLines = function () { var e = this.$getSelectedRows(), t = this.session, n = []; for (var r = e.first; r <= e.last; r++)n.push(t.getLine(r)); n.sort(function (e, t) { return e.toLowerCase() < t.toLowerCase() ? -1 : e.toLowerCase() > t.toLowerCase() ? 1 : 0 }); var i = new p(0, 0, 0, 0); for (var r = e.first; r <= e.last; r++) { var s = t.getLine(r); i.start.row = r, i.end.row = r, i.end.column = s.length, t.replace(i, n[r - e.first]) } }, this.toggleCommentLines = function () { var e = this.session.getState(this.getCursorPosition().row), t = this.$getSelectedRows(); this.session.getMode().toggleCommentLines(e, this.session, t.first, t.last) }, this.toggleBlockComment = function () { var e = this.getCursorPosition(), t = this.session.getState(e.row), n = this.getSelectionRange(); this.session.getMode().toggleBlockComment(t, this.session, n, e) }, this.getNumberAt = function (e, t) { var n = /[\-]?[0-9]+(?:\.[0-9]+)?/g; n.lastIndex = 0; var r = this.session.getLine(e); while (n.lastIndex < t) { var i = n.exec(r); if (i.index <= t && i.index + i[0].length >= t) { var s = { value: i[0], start: i.index, end: i.index + i[0].length }; return s } } return null }, this.modifyNumber = function (e) { var t = this.selection.getCursor().row, n = this.selection.getCursor().column, r = new p(t, n - 1, t, n), i = this.session.getTextRange(r); if (!isNaN(parseFloat(i)) && isFinite(i)) { var s = this.getNumberAt(t, n); if (s) { var o = s.value.indexOf(".") >= 0 ? s.start + s.value.indexOf(".") + 1 : s.end, u = s.start + s.value.length - o, a = parseFloat(s.value); a *= Math.pow(10, u), o !== s.end && n < o ? e *= Math.pow(10, s.end - n - 1) : e *= Math.pow(10, s.end - n), a += e, a /= Math.pow(10, u); var f = a.toFixed(u), l = new p(t, s.start, t, s.end); this.session.replace(l, f), this.moveCursorTo(t, Math.max(s.start + 1, n + f.length - s.value.length)) } } else this.toggleWord() }, this.$toggleWordPairs = [["first", "last"], ["true", "false"], ["yes", "no"], ["width", "height"], ["top", "bottom"], ["right", "left"], ["on", "off"], ["x", "y"], ["get", "set"], ["max", "min"], ["horizontal", "vertical"], ["show", "hide"], ["add", "remove"], ["up", "down"], ["before", "after"], ["even", "odd"], ["in", "out"], ["inside", "outside"], ["next", "previous"], ["increase", "decrease"], ["attach", "detach"], ["&&", "||"], ["==", "!="]], this.toggleWord = function () { var e = this.selection.getCursor().row, t = this.selection.getCursor().column; this.selection.selectWord(); var n = this.getSelectedText(), r = this.selection.getWordRange().start.column, i = n.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, "$1 ").split(/\s/), o = t - r - 1; o < 0 && (o = 0); var u = 0, a = 0, f = this; n.match(/[A-Za-z0-9_]+/) && i.forEach(function (t, i) { a = u + t.length, o >= u && o <= a && (n = t, f.selection.clearSelection(), f.moveCursorTo(e, u + r), f.selection.selectTo(e, a + r)), u = a }); var l = this.$toggleWordPairs, c; for (var h = 0; h < l.length; h++) { var p = l[h]; for (var d = 0; d <= 1; d++) { var v = +!d, m = n.match(new RegExp("^\\s?_?(" + s.escapeRegExp(p[d]) + ")\\s?$", "i")); if (m) { var g = n.match(new RegExp("([_]|^|\\s)(" + s.escapeRegExp(m[1]) + ")($|\\s)", "g")); g && (c = n.replace(new RegExp(s.escapeRegExp(p[d]), "i"), function (e) { var t = p[v]; return e.toUpperCase() == e ? t = t.toUpperCase() : e.charAt(0).toUpperCase() == e.charAt(0) && (t = t.substr(0, 0) + p[v].charAt(0).toUpperCase() + t.substr(1)), t }), this.insert(c), c = "") } } } }, this.removeLines = function () { var e = this.$getSelectedRows(); this.session.removeFullLines(e.first, e.last), this.clearSelection() }, this.duplicateSelection = function () { var e = this.selection, t = this.session, n = e.getRange(), r = e.isBackwards(); if (n.isEmpty()) { var i = n.start.row; t.duplicateLines(i, i) } else { var s = r ? n.start : n.end, o = t.insert(s, t.getTextRange(n), !1); n.start = s, n.end = o, e.setSelectionRange(n, r) } }, this.moveLinesDown = function () { this.$moveLines(1, !1) }, this.moveLinesUp = function () { this.$moveLines(-1, !1) }, this.moveText = function (e, t, n) { return this.session.moveText(e, t, n) }, this.copyLinesUp = function () { this.$moveLines(-1, !0) }, this.copyLinesDown = function () { this.$moveLines(1, !0) }, this.$moveLines = function (e, t) { var n, r, i = this.selection; if (!i.inMultiSelectMode || this.inVirtualSelectionMode) { var s = i.toOrientedRange(); n = this.$getSelectedRows(s), r = this.session.$moveLines(n.first, n.last, t ? 0 : e), t && e == -1 && (r = 0), s.moveBy(r, 0), i.fromOrientedRange(s) } else { var o = i.rangeList.ranges; i.rangeList.detach(this.session), this.inVirtualSelectionMode = !0; var u = 0, a = 0, f = o.length; for (var l = 0; l < f; l++) { var c = l; o[l].moveBy(u, 0), n = this.$getSelectedRows(o[l]); var h = n.first, p = n.last; while (++l < f) { a && o[l].moveBy(a, 0); var d = this.$getSelectedRows(o[l]); if (t && d.first != p) break; if (!t && d.first > p + 1) break; p = d.last } l--, u = this.session.$moveLines(h, p, t ? 0 : e), t && e == -1 && (c = l + 1); while (c <= l) o[c].moveBy(u, 0), c++; t || (u = 0), a += u } i.fromOrientedRange(i.ranges[0]), i.rangeList.attach(this.session), this.inVirtualSelectionMode = !1 } }, this.$getSelectedRows = function (e) { return e = (e || this.getSelectionRange()).collapseRows(), { first: this.session.getRowFoldStart(e.start.row), last: this.session.getRowFoldEnd(e.end.row) } }, this.onCompositionStart = function (e) { this.renderer.showComposition(e) }, this.onCompositionUpdate = function (e) { this.renderer.setCompositionText(e) }, this.onCompositionEnd = function () { this.renderer.hideComposition() }, this.getFirstVisibleRow = function () { return this.renderer.getFirstVisibleRow() }, this.getLastVisibleRow = function () { return this.renderer.getLastVisibleRow() }, this.isRowVisible = function (e) { return e >= this.getFirstVisibleRow() && e <= this.getLastVisibleRow() }, this.isRowFullyVisible = function (e) { return e >= this.renderer.getFirstFullyVisibleRow() && e <= this.renderer.getLastFullyVisibleRow() }, this.$getVisibleRowCount = function () { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1 }, this.$moveByPage = function (e, t) { var n = this.renderer, r = this.renderer.layerConfig, i = e * Math.floor(r.height / r.lineHeight); t === !0 ? this.selection.$moveSelection(function () { this.moveCursorBy(i, 0) }) : t === !1 && (this.selection.moveCursorBy(i, 0), this.selection.clearSelection()); var s = n.scrollTop; n.scrollBy(0, i * r.lineHeight), t != null && n.scrollCursorIntoView(null, .5), n.animateScrolling(s) }, this.selectPageDown = function () { this.$moveByPage(1, !0) }, this.selectPageUp = function () { this.$moveByPage(-1, !0) }, this.gotoPageDown = function () { this.$moveByPage(1, !1) }, this.gotoPageUp = function () { this.$moveByPage(-1, !1) }, this.scrollPageDown = function () { this.$moveByPage(1) }, this.scrollPageUp = function () { this.$moveByPage(-1) }, this.scrollToRow = function (e) { this.renderer.scrollToRow(e) }, this.scrollToLine = function (e, t, n, r) { this.renderer.scrollToLine(e, t, n, r) }, this.centerSelection = function () { var e = this.getSelectionRange(), t = { row: Math.floor(e.start.row + (e.end.row - e.start.row) / 2), column: Math.floor(e.start.column + (e.end.column - e.start.column) / 2) }; this.renderer.alignCursor(t, .5) }, this.getCursorPosition = function () { return this.selection.getCursor() }, this.getCursorPositionScreen = function () { return this.session.documentToScreenPosition(this.getCursorPosition()) }, this.getSelectionRange = function () { return this.selection.getRange() }, this.selectAll = function () { this.selection.selectAll() }, this.clearSelection = function () { this.selection.clearSelection() }, this.moveCursorTo = function (e, t) { this.selection.moveCursorTo(e, t) }, this.moveCursorToPosition = function (e) { this.selection.moveCursorToPosition(e) }, this.jumpToMatching = function (e, t) { var n = this.getCursorPosition(), r = new y(this.session, n.row, n.column), i = r.getCurrentToken(), s = i || r.stepForward(); if (!s) return; var o, u = !1, a = {}, f = n.column - s.start, l, c = { ")": "(", "(": "(", "]": "[", "[": "[", "{": "{", "}": "{" }; do { if (s.value.match(/[{}()\[\]]/g)) for (; f < s.value.length && !u; f++) { if (!c[s.value[f]]) continue; l = c[s.value[f]] + "." + s.type.replace("rparen", "lparen"), isNaN(a[l]) && (a[l] = 0); switch (s.value[f]) { case "(": case "[": case "{": a[l]++; break; case ")": case "]": case "}": a[l]--, a[l] === -1 && (o = "bracket", u = !0) } } else s.type.indexOf("tag-name") !== -1 && (isNaN(a[s.value]) && (a[s.value] = 0), i.value === "<" ? a[s.value]++ : i.value === "= 0; --s)this.$tryReplace(n[s], e) && r++; return this.selection.setSelectionRange(i), r }, this.$tryReplace = function (e, t) { var n = this.session.getTextRange(e); return t = this.$search.replace(n, t), t !== null ? (e.end = this.session.replace(e, t), e) : null }, this.getLastSearchOptions = function () { return this.$search.getOptions() }, this.find = function (e, t, n) { t || (t = {}), typeof e == "string" || e instanceof RegExp ? t.needle = e : typeof e == "object" && r.mixin(t, e); var i = this.selection.getRange(); t.needle == null && (e = this.session.getTextRange(i) || this.$search.$options.needle, e || (i = this.session.getWordRange(i.start.row, i.start.column), e = this.session.getTextRange(i)), this.$search.set({ needle: e })), this.$search.set(t), t.start || this.$search.set({ start: i }); var s = this.$search.find(this.session); if (t.preventScroll) return s; if (s) return this.revealRange(s, n), s; t.backwards ? i.start = i.end : i.end = i.start, this.selection.setRange(i) }, this.findNext = function (e, t) { this.find({ skipCurrent: !0, backwards: !1 }, e, t) }, this.findPrevious = function (e, t) { this.find(e, { skipCurrent: !0, backwards: !0 }, t) }, this.revealRange = function (e, t) { this.session.unfold(e), this.selection.setSelectionRange(e); var n = this.renderer.scrollTop; this.renderer.scrollSelectionIntoView(e.start, e.end, .5), t !== !1 && this.renderer.animateScrolling(n) }, this.undo = function () { this.session.getUndoManager().undo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.redo = function () { this.session.getUndoManager().redo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.destroy = function () { this.$toDestroy && (this.$toDestroy.forEach(function (e) { e.destroy() }), this.$toDestroy = null), this.renderer.destroy(), this._signal("destroy", this), this.session && this.session.destroy(), this._$emitInputEvent && this._$emitInputEvent.cancel(), this.removeAllListeners() }, this.setAutoScrollEditorIntoView = function (e) { if (!e) return; var t, n = this, r = !1; this.$scrollAnchor || (this.$scrollAnchor = document.createElement("div")); var i = this.$scrollAnchor; i.style.cssText = "position:absolute", this.container.insertBefore(i, this.container.firstChild); var s = this.on("changeSelection", function () { r = !0 }), o = this.renderer.on("beforeRender", function () { r && (t = n.renderer.container.getBoundingClientRect()) }), u = this.renderer.on("afterRender", function () { if (r && t && (n.isFocused() || n.searchBox && n.searchBox.isFocused())) { var e = n.renderer, s = e.$cursorLayer.$pixelPos, o = e.layerConfig, u = s.top - o.offset; s.top >= 0 && u + t.top < 0 ? r = !0 : s.top < o.height && s.top + t.top + o.lineHeight > window.innerHeight ? r = !1 : r = null, r != null && (i.style.top = u + "px", i.style.left = s.left + "px", i.style.height = o.lineHeight + "px", i.scrollIntoView(r)), r = t = null } }); this.setAutoScrollEditorIntoView = function (e) { if (e) return; delete this.setAutoScrollEditorIntoView, this.off("changeSelection", s), this.renderer.off("afterRender", u), this.renderer.off("beforeRender", o) } }, this.$resetCursorStyle = function () { var e = this.$cursorStyle || "ace", t = this.renderer.$cursorLayer; if (!t) return; t.setSmoothBlinking(/smooth/.test(e)), t.isBlinking = !this.$readOnly && e != "wide", i.setCssClass(t.element, "ace_slim-cursors", /slim/.test(e)) }, this.prompt = function (e, t, n) { var r = this; g.loadModule("./ext/prompt", function (i) { i.prompt(r, e, t, n) }) } }.call(w.prototype), g.defineOptions(w.prototype, "editor", { selectionStyle: { set: function (e) { this.onSelectionChange(), this._signal("changeSelectionStyle", { data: e }) }, initialValue: "line" }, highlightActiveLine: { set: function () { this.$updateHighlightActiveLine() }, initialValue: !0 }, highlightSelectedWord: { set: function (e) { this.$onSelectionChange() }, initialValue: !0 }, readOnly: { set: function (e) { this.textInput.setReadOnly(e), this.$resetCursorStyle() }, initialValue: !1 }, copyWithEmptySelection: { set: function (e) { this.textInput.setCopyWithEmptySelection(e) }, initialValue: !1 }, cursorStyle: { set: function (e) { this.$resetCursorStyle() }, values: ["ace", "slim", "smooth", "wide"], initialValue: "ace" }, mergeUndoDeltas: { values: [!1, !0, "always"], initialValue: !0 }, behavioursEnabled: { initialValue: !0 }, wrapBehavioursEnabled: { initialValue: !0 }, enableAutoIndent: { initialValue: !0 }, autoScrollEditorIntoView: { set: function (e) { this.setAutoScrollEditorIntoView(e) } }, keyboardHandler: { set: function (e) { this.setKeyboardHandler(e) }, get: function () { return this.$keybindingId }, handlesSet: !0 }, value: { set: function (e) { this.session.setValue(e) }, get: function () { return this.getValue() }, handlesSet: !0, hidden: !0 }, session: { set: function (e) { this.setSession(e) }, get: function () { return this.session }, handlesSet: !0, hidden: !0 }, showLineNumbers: { set: function (e) { this.renderer.$gutterLayer.setShowLineNumbers(e), this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER), e && this.$relativeLineNumbers ? E.attach(this) : E.detach(this) }, initialValue: !0 }, relativeLineNumbers: { set: function (e) { this.$showLineNumbers && e ? E.attach(this) : E.detach(this) } }, placeholder: { set: function (e) { this.$updatePlaceholder || (this.$updatePlaceholder = function () { var e = this.session && (this.renderer.$composition || this.getValue()); if (e && this.renderer.placeholderNode) this.renderer.off("afterRender", this.$updatePlaceholder), i.removeCssClass(this.container, "ace_hasPlaceholder"), this.renderer.placeholderNode.remove(), this.renderer.placeholderNode = null; else if (!e && !this.renderer.placeholderNode) { this.renderer.on("afterRender", this.$updatePlaceholder), i.addCssClass(this.container, "ace_hasPlaceholder"); var t = i.createElement("div"); t.className = "ace_placeholder", t.textContent = this.$placeholder || "", this.renderer.placeholderNode = t, this.renderer.content.appendChild(this.renderer.placeholderNode) } else !e && this.renderer.placeholderNode && (this.renderer.placeholderNode.textContent = this.$placeholder || "") }.bind(this), this.on("input", this.$updatePlaceholder)), this.$updatePlaceholder() } }, hScrollBarAlwaysVisible: "renderer", vScrollBarAlwaysVisible: "renderer", highlightGutterLine: "renderer", animatedScroll: "renderer", showInvisibles: "renderer", showPrintMargin: "renderer", printMarginColumn: "renderer", printMargin: "renderer", fadeFoldWidgets: "renderer", showFoldWidgets: "renderer", displayIndentGuides: "renderer", showGutter: "renderer", fontSize: "renderer", fontFamily: "renderer", maxLines: "renderer", minLines: "renderer", scrollPastEnd: "renderer", fixedWidthGutter: "renderer", theme: "renderer", hasCssTransforms: "renderer", maxPixelHeight: "renderer", useTextareaForIME: "renderer", scrollSpeed: "$mouseHandler", dragDelay: "$mouseHandler", dragEnabled: "$mouseHandler", focusTimeout: "$mouseHandler", tooltipFollowsMouse: "$mouseHandler", firstLineNumber: "session", overwrite: "session", newLineMode: "session", useWorker: "session", useSoftTabs: "session", navigateWithinSoftTabs: "session", tabSize: "session", wrap: "session", indentedSoftWrap: "session", foldStyle: "session", mode: "session" }); var E = { getText: function (e, t) { return (Math.abs(e.selection.lead.row - t) || t + 1 + (t < 9 ? "\u00b7" : "")) + "" }, getWidth: function (e, t, n) { return Math.max(t.toString().length, (n.lastRow + 1).toString().length, 2) * n.characterWidth }, update: function (e, t) { t.renderer.$loop.schedule(t.renderer.CHANGE_GUTTER) }, attach: function (e) { e.renderer.$gutterLayer.$renderer = this, e.on("changeSelection", this.update), this.update(null, e) }, detach: function (e) { e.renderer.$gutterLayer.$renderer == this && (e.renderer.$gutterLayer.$renderer = null), e.off("changeSelection", this.update), this.update(null, e) } }; t.Editor = w }), define("ace/undomanager", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { for (var n = t; n--;) { var r = e[n]; if (r && !r[0].ignore) { while (n < t - 1) { var i = d(e[n], e[n + 1]); e[n] = i[0], e[n + 1] = i[1], n++ } return !0 } } } function a(e) { var t = e.action == "insert", n = e.start, r = e.end, i = (r.row - n.row) * (t ? 1 : -1), s = (r.column - n.column) * (t ? 1 : -1); t && (r = n); for (var o in this.marks) { var a = this.marks[o], f = u(a, n); if (f < 0) continue; if (f === 0 && t) { if (a.bias != 1) { a.bias == -1; continue } f = 1 } var l = t ? f : u(a, r); if (l > 0) { a.row += i, a.column += a.row == r.row ? s : 0; continue } !t && l <= 0 && (a.row = n.row, a.column = n.column, l === 0 && (a.bias = 1)) } } function f(e) { return { row: e.row, column: e.column } } function l(e) { return { start: f(e.start), end: f(e.end), action: e.action, lines: e.lines.slice() } } function c(e) { e = e || this; if (Array.isArray(e)) return e.map(c).join("\n"); var t = ""; e.action ? (t = e.action == "insert" ? "+" : "-", t += "[" + e.lines + "]") : e.value && (Array.isArray(e.value) ? t = e.value.map(h).join("\n") : t = h(e.value)), e.start && (t += h(e)); if (e.id || e.rev) t += " (" + (e.id || e.rev) + ")"; return t } function h(e) { return e.start.row + ":" + e.start.column + "=>" + e.end.row + ":" + e.end.column } function p(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } else if (!n && r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (!n && !r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } return [t, e] } function d(e, t) { for (var n = e.length; n--;)for (var r = 0; r < t.length; r++)if (!p(e[n], t[r])) { while (n < e.length) { while (r--) p(t[r], e[n]); r = t.length, n++ } return [e, t] } return e.selectionBefore = t.selectionBefore = e.selectionAfter = t.selectionAfter = null, [t, e] } function v(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) o(e.start, t.start) < 0 ? m(t, e, 1) : m(e, t, 1); else if (n && !r) o(e.start, t.end) >= 0 ? m(e, t, -1) : o(e.start, t.start) <= 0 ? m(t, e, 1) : (m(e, s.fromPoints(t.start, e.start), -1), m(t, e, 1)); else if (!n && r) o(t.start, e.end) >= 0 ? m(t, e, -1) : o(t.start, e.start) <= 0 ? m(e, t, 1) : (m(t, s.fromPoints(e.start, t.start), -1), m(e, t, 1)); else if (!n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) { var i, u; return o(e.start, t.start) < 0 && (i = e, e = y(e, t.start)), o(e.end, t.end) > 0 && (u = y(e, t.end)), g(t.end, e.start, e.end, -1), u && !i && (e.lines = u.lines, e.start = u.start, e.end = u.end, u = e), [t, i, u].filter(Boolean) } m(e, t, -1) } return [t, e] } function m(e, t, n) { g(e.start, t.start, t.end, n), g(e.end, t.start, t.end, n) } function g(e, t, n, r) { e.row == (r == 1 ? t : n).row && (e.column += r * (n.column - t.column)), e.row += r * (n.row - t.row) } function y(e, t) { var n = e.lines, r = e.end; e.end = f(t); var i = e.end.row - e.start.row, s = n.splice(i, n.length), o = i ? t.column : t.column - e.start.column; n.push(s[0].substring(0, o)), s[0] = s[0].substr(o); var u = { start: f(t), end: r, lines: s, action: e.action }; return u } function b(e, t) { t = l(t); for (var n = e.length; n--;) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i], o = v(s, t); t = o[0], o.length != 2 && (o[2] ? (r.splice(i + 1, 1, o[1], o[2]), i++) : o[1] || (r.splice(i, 1), i--)) } r.length || e.splice(n, 1) } return e } function w(e, t) { for (var n = 0; n < t.length; n++) { var r = t[n]; for (var i = 0; i < r.length; i++)b(e, r[i]) } } var r = function () { this.$maxRev = 0, this.$fromUndo = !1, this.reset() }; (function () { this.addSession = function (e) { this.$session = e }, this.add = function (e, t, n) { if (this.$fromUndo) return; if (e == this.$lastDelta) return; this.$keepRedoStack || (this.$redoStack.length = 0); if (t === !1 || !this.lastDeltas) this.lastDeltas = [], this.$undoStack.push(this.lastDeltas), e.id = this.$rev = ++this.$maxRev; if (e.action == "remove" || e.action == "insert") this.$lastDelta = e; this.lastDeltas.push(e) }, this.addSelection = function (e, t) { this.selections.push({ value: e, rev: t || this.$rev }) }, this.startNewGroup = function () { return this.lastDeltas = null, this.$rev }, this.markIgnored = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack; for (var r = n.length; r--;) { var i = n[r][0]; if (i.id <= e) break; i.id < t && (i.ignore = !0) } this.lastDeltas = null }, this.getSelection = function (e, t) { var n = this.selections; for (var r = n.length; r--;) { var i = n[r]; if (i.rev < e) return t && (i = n[r + 1]), i } }, this.getRevision = function () { return this.$rev }, this.getDeltas = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack, r = null, i = 0; for (var s = n.length; s--;) { var o = n[s][0]; o.id < t && !r && (r = s + 1); if (o.id <= e) { i = s + 1; break } } return n.slice(i, r) }, this.getChangedRanges = function (e, t) { t == null && (t = this.$rev + 1) }, this.getChangedLines = function (e, t) { t == null && (t = this.$rev + 1) }, this.undo = function (e, t) { this.lastDeltas = null; var n = this.$undoStack; if (!i(n, n.length)) return; e || (e = this.$session), this.$redoStackBaseRev !== this.$rev && this.$redoStack.length && (this.$redoStack = []), this.$fromUndo = !0; var r = n.pop(), s = null; return r && (s = e.undoChanges(r, t), this.$redoStack.push(r), this.$syncRev()), this.$fromUndo = !1, s }, this.redo = function (e, t) { this.lastDeltas = null, e || (e = this.$session), this.$fromUndo = !0; if (this.$redoStackBaseRev != this.$rev) { var n = this.getDeltas(this.$redoStackBaseRev, this.$rev + 1); w(this.$redoStack, n), this.$redoStackBaseRev = this.$rev, this.$redoStack.forEach(function (e) { e[0].id = ++this.$maxRev }, this) } var r = this.$redoStack.pop(), i = null; return r && (i = e.redoChanges(r, t), this.$undoStack.push(r), this.$syncRev()), this.$fromUndo = !1, i }, this.$syncRev = function () { var e = this.$undoStack, t = e[e.length - 1], n = t && t[0].id || 0; this.$redoStackBaseRev = n, this.$rev = n }, this.reset = function () { this.lastDeltas = null, this.$lastDelta = null, this.$undoStack = [], this.$redoStack = [], this.$rev = 0, this.mark = 0, this.$redoStackBaseRev = this.$rev, this.selections = [] }, this.canUndo = function () { return this.$undoStack.length > 0 }, this.canRedo = function () { return this.$redoStack.length > 0 }, this.bookmark = function (e) { e == undefined && (e = this.$rev), this.mark = e }, this.isAtBookmark = function () { return this.$rev === this.mark }, this.toJSON = function () { }, this.fromJSON = function () { }, this.hasUndo = this.canUndo, this.hasRedo = this.canRedo, this.isClean = this.isAtBookmark, this.markClean = this.bookmark, this.$prettyPrint = function (e) { return e ? c(e) : c(this.$undoStack) + "\n---\n" + c(this.$redoStack) } }).call(r.prototype); var s = e("./range").Range, o = s.comparePoints, u = s.comparePoints; t.UndoManager = r }), define("ace/layer/lines", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e, t) { this.element = e, this.canvasHeight = t || 5e5, this.element.style.height = this.canvasHeight * 2 + "px", this.cells = [], this.cellCache = [], this.$offsetCoefficient = 0 }; (function () { this.moveContainer = function (e) { r.translate(this.element, 0, -(e.firstRowScreen * e.lineHeight % this.canvasHeight) - e.offset * this.$offsetCoefficient) }, this.pageChanged = function (e, t) { return Math.floor(e.firstRowScreen * e.lineHeight / this.canvasHeight) !== Math.floor(t.firstRowScreen * t.lineHeight / this.canvasHeight) }, this.computeLineTop = function (e, t, n) { var r = t.firstRowScreen * t.lineHeight, i = Math.floor(r / this.canvasHeight), s = n.documentToScreenRow(e, 0) * t.lineHeight; return s - i * this.canvasHeight }, this.computeLineHeight = function (e, t, n) { return t.lineHeight * n.getRowLineCount(e) }, this.getLength = function () { return this.cells.length }, this.get = function (e) { return this.cells[e] }, this.shift = function () { this.$cacheCell(this.cells.shift()) }, this.pop = function () { this.$cacheCell(this.cells.pop()) }, this.push = function (e) { if (Array.isArray(e)) { this.cells.push.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.appendChild(t) } else this.cells.push(e), this.element.appendChild(e.element) }, this.unshift = function (e) { if (Array.isArray(e)) { this.cells.unshift.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.firstChild ? this.element.insertBefore(t, this.element.firstChild) : this.element.appendChild(t) } else this.cells.unshift(e), this.element.insertAdjacentElement("afterbegin", e.element) }, this.last = function () { return this.cells.length ? this.cells[this.cells.length - 1] : null }, this.$cacheCell = function (e) { if (!e) return; e.element.remove(), this.cellCache.push(e) }, this.createCell = function (e, t, n, i) { var s = this.cellCache.pop(); if (!s) { var o = r.createElement("div"); i && i(o), this.element.appendChild(o), s = { element: o, text: "", row: e } } return s.row = e, s } }).call(i.prototype), t.Lines = i }), define("ace/layer/gutter", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/layer/lines"], function (e, t, n) { "use strict"; function f(e) { var t = document.createTextNode(""); e.appendChild(t); var n = r.createElement("span"); return e.appendChild(n), e } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/lang"), o = e("../lib/event_emitter").EventEmitter, u = e("./lines").Lines, a = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_gutter-layer", e.appendChild(this.element), this.setShowFoldWidgets(this.$showFoldWidgets), this.gutterWidth = 0, this.$annotations = [], this.$updateAnnotations = this.$updateAnnotations.bind(this), this.$lines = new u(this.element), this.$lines.$offsetCoefficient = 1 }; (function () { i.implement(this, o), this.setSession = function (e) { this.session && this.session.off("change", this.$updateAnnotations), this.session = e, e && e.on("change", this.$updateAnnotations) }, this.addGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.addGutterDecoration"), this.session.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.removeGutterDecoration"), this.session.removeGutterDecoration(e, t) }, this.setAnnotations = function (e) { this.$annotations = []; for (var t = 0; t < e.length; t++) { var n = e[t], r = n.row, i = this.$annotations[r]; i || (i = this.$annotations[r] = { text: [] }); var o = n.text; o = o ? s.escapeHTML(o) : n.html || "", i.text.indexOf(o) === -1 && i.text.push(o); var u = n.type; u == "error" ? i.className = " ace_error" : u == "warning" && i.className != " ace_error" ? i.className = " ace_warning" : u == "info" && !i.className && (i.className = " ace_info") } }, this.$updateAnnotations = function (e) { if (!this.$annotations.length) return; var t = e.start.row, n = e.end.row - t; if (n !== 0) if (e.action == "remove") this.$annotations.splice(t, n + 1, null); else { var r = new Array(n + 1); r.unshift(t, 1), this.$annotations.splice.apply(this.$annotations, r) } }, this.update = function (e) { this.config = e; var t = this.session, n = e.firstRow, r = Math.min(e.lastRow + e.gutterOffset, t.getLength() - 1); this.oldLastRow = r, this.config = e, this.$lines.moveContainer(e), this.$updateCursorRow(); var i = t.getNextFoldLine(n), s = i ? i.start.row : Infinity, o = null, u = -1, a = n; for (; ;) { a > s && (a = i.end.row + 1, i = t.getNextFoldLine(a, i), s = i ? i.start.row : Infinity); if (a > r) { while (this.$lines.getLength() > u + 1) this.$lines.pop(); break } o = this.$lines.get(++u), o ? o.row = a : (o = this.$lines.createCell(a, e, this.session, f), this.$lines.push(o)), this.$renderCell(o, e, i, a), a++ } this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$updateGutterWidth = function (e) { var t = this.session, n = t.gutterRenderer || this.$renderer, r = t.$firstLineNumber, i = this.$lines.last() ? this.$lines.last().text : ""; if (this.$fixedWidth || t.$useWrapMode) i = t.getLength() + r - 1; var s = n ? n.getWidth(t, i, e) : i.toString().length * e.characterWidth, o = this.$padding || this.$computePadding(); s += o.left + o.right, s !== this.gutterWidth && !isNaN(s) && (this.gutterWidth = s, this.element.parentNode.style.width = this.element.style.width = Math.ceil(this.gutterWidth) + "px", this._signal("changeGutterWidth", s)) }, this.$updateCursorRow = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.getCursor(); if (this.$cursorRow === e.row) return; this.$cursorRow = e.row }, this.updateLineHighlight = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.cursor.row; this.$cursorRow = e; if (this.$cursorCell && this.$cursorCell.row == e) return; this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")); var t = this.$lines.cells; this.$cursorCell = null; for (var n = 0; n < t.length; n++) { var r = t[n]; if (r.row >= this.$cursorRow) { if (r.row > this.$cursorRow) { var i = this.session.getFoldLine(this.$cursorRow); if (!(n > 0 && i && i.start.row == t[n - 1].row)) break; r = t[n - 1] } r.element.className = "ace_gutter-active-line " + r.element.className, this.$cursorCell = r; break } } }, this.scrollLines = function (e) { var t = this.config; this.config = e, this.$updateCursorRow(); if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = Math.min(e.lastRow + e.gutterOffset, this.session.getLength() - 1), r = this.oldLastRow; this.oldLastRow = n; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (r > n) for (var i = this.session.getFoldedRowCount(n + 1, r); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLines(e, e.firstRow, t.firstRow - 1)), n > r && this.$lines.push(this.$renderLines(e, r + 1, n)), this.updateLineHighlight(), this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$renderLines = function (e, t, n) { var r = [], i = t, s = this.session.getNextFoldLine(i), o = s ? s.start.row : Infinity; for (; ;) { i > o && (i = s.end.row + 1, s = this.session.getNextFoldLine(i, s), o = s ? s.start.row : Infinity); if (i > n) break; var u = this.$lines.createCell(i, e, this.session, f); this.$renderCell(u, e, s, i), r.push(u), i++ } return r }, this.$renderCell = function (e, t, n, i) { var s = e.element, o = this.session, u = s.childNodes[0], a = s.childNodes[1], f = o.$firstLineNumber, l = o.$breakpoints, c = o.$decorations, h = o.gutterRenderer || this.$renderer, p = this.$showFoldWidgets && o.foldWidgets, d = n ? n.start.row : Number.MAX_VALUE, v = "ace_gutter-cell "; this.$highlightGutterLine && (i == this.$cursorRow || n && i < this.$cursorRow && i >= d && this.$cursorRow <= n.end.row) && (v += "ace_gutter-active-line ", this.$cursorCell != e && (this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")), this.$cursorCell = e)), l[i] && (v += l[i]), c[i] && (v += c[i]), this.$annotations[i] && (v += this.$annotations[i].className), s.className != v && (s.className = v); if (p) { var m = p[i]; m == null && (m = p[i] = o.getFoldWidget(i)) } if (m) { var v = "ace_fold-widget ace_" + m; m == "start" && i == d && i < n.end.row ? v += " ace_closed" : v += " ace_open", a.className != v && (a.className = v); var g = t.lineHeight + "px"; r.setStyle(a.style, "height", g), r.setStyle(a.style, "display", "inline-block") } else a && r.setStyle(a.style, "display", "none"); var y = (h ? h.getText(o, i) : i + f).toString(); return y !== u.data && (u.data = y), r.setStyle(e.element.style, "height", this.$lines.computeLineHeight(i, t, o) + "px"), r.setStyle(e.element.style, "top", this.$lines.computeLineTop(i, t, o) + "px"), e.text = y, e }, this.$fixedWidth = !1, this.$highlightGutterLine = !0, this.$renderer = "", this.setHighlightGutterLine = function (e) { this.$highlightGutterLine = e }, this.$showLineNumbers = !0, this.$renderer = "", this.setShowLineNumbers = function (e) { this.$renderer = !e && { getWidth: function () { return 0 }, getText: function () { return "" } } }, this.getShowLineNumbers = function () { return this.$showLineNumbers }, this.$showFoldWidgets = !0, this.setShowFoldWidgets = function (e) { e ? r.addCssClass(this.element, "ace_folding-enabled") : r.removeCssClass(this.element, "ace_folding-enabled"), this.$showFoldWidgets = e, this.$padding = null }, this.getShowFoldWidgets = function () { return this.$showFoldWidgets }, this.$computePadding = function () { if (!this.element.firstChild) return { left: 0, right: 0 }; var e = r.computedStyle(this.element.firstChild); return this.$padding = {}, this.$padding.left = (parseInt(e.borderLeftWidth) || 0) + (parseInt(e.paddingLeft) || 0) + 1, this.$padding.right = (parseInt(e.borderRightWidth) || 0) + (parseInt(e.paddingRight) || 0), this.$padding }, this.getRegion = function (e) { var t = this.$padding || this.$computePadding(), n = this.element.getBoundingClientRect(); if (e.x < t.left + n.left) return "markers"; if (this.$showFoldWidgets && e.x > n.right - t.right) return "foldWidgets" } }).call(a.prototype), t.Gutter = a }), define("ace/layer/marker", ["require", "exports", "module", "ace/range", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = e("../lib/dom"), s = function (e) { this.element = i.createElement("div"), this.element.className = "ace_layer ace_marker-layer", e.appendChild(this.element) }; (function () { function e(e, t, n, r) { return (e ? 1 : 0) | (t ? 2 : 0) | (n ? 4 : 0) | (r ? 8 : 0) } this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setMarkers = function (e) { this.markers = e }, this.elt = function (e, t) { var n = this.i != -1 && this.element.childNodes[this.i]; n ? this.i++ : (n = document.createElement("div"), this.element.appendChild(n), this.i = -1), n.style.cssText = t, n.className = e }, this.update = function (e) { if (!e) return; this.config = e, this.i = 0; var t; for (var n in this.markers) { var r = this.markers[n]; if (!r.range) { r.update(t, this, this.session, e); continue } var i = r.range.clipRows(e.firstRow, e.lastRow); if (i.isEmpty()) continue; i = i.toScreenRange(this.session); if (r.renderer) { var s = this.$getTop(i.start.row, e), o = this.$padding + i.start.column * e.characterWidth; r.renderer(t, i, o, s, e) } else r.type == "fullLine" ? this.drawFullLineMarker(t, i, r.clazz, e) : r.type == "screenLine" ? this.drawScreenLineMarker(t, i, r.clazz, e) : i.isMultiLine() ? r.type == "text" ? this.drawTextMarker(t, i, r.clazz, e) : this.drawMultiLineMarker(t, i, r.clazz, e) : this.drawSingleLineMarker(t, i, r.clazz + " ace_start" + " ace_br15", e) } if (this.i != -1) while (this.i < this.element.childElementCount) this.element.removeChild(this.element.lastChild) }, this.$getTop = function (e, t) { return (e - t.firstRowScreen) * t.lineHeight }, this.drawTextMarker = function (t, n, i, s, o) { var u = this.session, a = n.start.row, f = n.end.row, l = a, c = 0, h = 0, p = u.getScreenLastRowColumn(l), d = new r(l, n.start.column, l, h); for (; l <= f; l++)d.start.row = d.end.row = l, d.start.column = l == a ? n.start.column : u.getRowWrapIndent(l), d.end.column = p, c = h, h = p, p = l + 1 < f ? u.getScreenLastRowColumn(l + 1) : l == f ? 0 : n.end.column, this.drawSingleLineMarker(t, d, i + (l == a ? " ace_start" : "") + " ace_br" + e(l == a || l == a + 1 && n.start.column, c < h, h > p, l == f), s, l == f ? 0 : 1, o) }, this.drawMultiLineMarker = function (e, t, n, r, i) { var s = this.$padding, o = r.lineHeight, u = this.$getTop(t.start.row, r), a = s + t.start.column * r.characterWidth; i = i || ""; if (this.session.$bidiHandler.isBidiRow(t.start.row)) { var f = t.clone(); f.end.row = f.start.row, f.end.column = this.session.getLine(f.start.row).length, this.drawBidiSingleLineMarker(e, f, n + " ace_br1 ace_start", r, null, i) } else this.elt(n + " ace_br1 ace_start", "height:" + o + "px;" + "right:0;" + "top:" + u + "px;left:" + a + "px;" + (i || "")); if (this.session.$bidiHandler.isBidiRow(t.end.row)) { var f = t.clone(); f.start.row = f.end.row, f.start.column = 0, this.drawBidiSingleLineMarker(e, f, n + " ace_br12", r, null, i) } else { u = this.$getTop(t.end.row, r); var l = t.end.column * r.characterWidth; this.elt(n + " ace_br12", "height:" + o + "px;" + "width:" + l + "px;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) } o = (t.end.row - t.start.row - 1) * r.lineHeight; if (o <= 0) return; u = this.$getTop(t.start.row + 1, r); var c = (t.start.column ? 1 : 0) | (t.end.column ? 0 : 8); this.elt(n + (c ? " ace_br" + c : ""), "height:" + o + "px;" + "right:0;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) }, this.drawSingleLineMarker = function (e, t, n, r, i, s) { if (this.session.$bidiHandler.isBidiRow(t.start.row)) return this.drawBidiSingleLineMarker(e, t, n, r, i, s); var o = r.lineHeight, u = (t.end.column + (i || 0) - t.start.column) * r.characterWidth, a = this.$getTop(t.start.row, r), f = this.$padding + t.start.column * r.characterWidth; this.elt(n, "height:" + o + "px;" + "width:" + u + "px;" + "top:" + a + "px;" + "left:" + f + "px;" + (s || "")) }, this.drawBidiSingleLineMarker = function (e, t, n, r, i, s) { var o = r.lineHeight, u = this.$getTop(t.start.row, r), a = this.$padding, f = this.session.$bidiHandler.getSelections(t.start.column, t.end.column); f.forEach(function (e) { this.elt(n, "height:" + o + "px;" + "width:" + e.width + (i || 0) + "px;" + "top:" + u + "px;" + "left:" + (a + e.left) + "px;" + (s || "")) }, this) }, this.drawFullLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; t.start.row != t.end.row && (o += this.$getTop(t.end.row, r) - s), this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) }, this.drawScreenLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) } }).call(s.prototype), t.Marker = s }), define("ace/layer/text", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/layer/lines", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("./lines").Lines, u = e("../lib/event_emitter").EventEmitter, a = function (e) { this.dom = i, this.element = this.dom.createElement("div"), this.element.className = "ace_layer ace_text-layer", e.appendChild(this.element), this.$updateEolChar = this.$updateEolChar.bind(this), this.$lines = new o(this.element) }; (function () { r.implement(this, u), this.EOF_CHAR = "\u00b6", this.EOL_CHAR_LF = "\u00ac", this.EOL_CHAR_CRLF = "\u00a4", this.EOL_CHAR = this.EOL_CHAR_LF, this.TAB_CHAR = "\u2014", this.SPACE_CHAR = "\u00b7", this.$padding = 0, this.MAX_LINE_LENGTH = 1e4, this.$updateEolChar = function () { var e = this.session.doc, t = e.getNewLineCharacter() == "\n" && e.getNewLineMode() != "windows", n = t ? this.EOL_CHAR_LF : this.EOL_CHAR_CRLF; if (this.EOL_CHAR != n) return this.EOL_CHAR = n, !0 }, this.setPadding = function (e) { this.$padding = e, this.element.style.margin = "0 " + e + "px" }, this.getLineHeight = function () { return this.$fontMetrics.$characterSize.height || 0 }, this.getCharacterWidth = function () { return this.$fontMetrics.$characterSize.width || 0 }, this.$setFontMetrics = function (e) { this.$fontMetrics = e, this.$fontMetrics.on("changeCharacterSize", function (e) { this._signal("changeCharacterSize", e) }.bind(this)), this.$pollSizeChanges() }, this.checkForSizeChanges = function () { this.$fontMetrics.checkForSizeChanges() }, this.$pollSizeChanges = function () { return this.$pollSizeChangesTimer = this.$fontMetrics.$pollSizeChanges() }, this.setSession = function (e) { this.session = e, e && this.$computeTabString() }, this.showInvisibles = !1, this.showSpaces = !1, this.showTabs = !1, this.showEOL = !1, this.setShowInvisibles = function (e) { return this.showInvisibles == e ? !1 : (this.showInvisibles = e, typeof e == "string" ? (this.showSpaces = /tab/i.test(e), this.showTabs = /space/i.test(e), this.showEOL = /eol/i.test(e)) : this.showSpaces = this.showTabs = this.showEOL = e, this.$computeTabString(), !0) }, this.displayIndentGuides = !0, this.setDisplayIndentGuides = function (e) { return this.displayIndentGuides == e ? !1 : (this.displayIndentGuides = e, this.$computeTabString(), !0) }, this.$tabStrings = [], this.onChangeTabSize = this.$computeTabString = function () { var e = this.session.getTabSize(); this.tabSize = e; var t = this.$tabStrings = [0]; for (var n = 1; n < e + 1; n++)if (this.showTabs) { var r = this.dom.createElement("span"); r.className = "ace_invisible ace_invisible_tab", r.textContent = s.stringRepeat(this.TAB_CHAR, n), t.push(r) } else t.push(this.dom.createTextNode(s.stringRepeat(" ", n), this.element)); if (this.displayIndentGuides) { this.$indentGuideRe = /\s\S| \t|\t |\s$/; var i = "ace_indent-guide", o = this.showSpaces ? " ace_invisible ace_invisible_space" : "", u = this.showSpaces ? s.stringRepeat(this.SPACE_CHAR, this.tabSize) : s.stringRepeat(" ", this.tabSize), a = this.showTabs ? " ace_invisible ace_invisible_tab" : "", f = this.showTabs ? s.stringRepeat(this.TAB_CHAR, this.tabSize) : u, r = this.dom.createElement("span"); r.className = i + o, r.textContent = u, this.$tabStrings[" "] = r; var r = this.dom.createElement("span"); r.className = i + a, r.textContent = f, this.$tabStrings[" "] = r } }, this.updateLines = function (e, t, n) { if (this.config.lastRow != e.lastRow || this.config.firstRow != e.firstRow) return this.update(e); this.config = e; var r = Math.max(t, e.firstRow), i = Math.min(n, e.lastRow), s = this.element.childNodes, o = 0; for (var u = e.firstRow; u < r; u++) { var a = this.session.getFoldLine(u); if (a) { if (a.containsRow(r)) { r = a.start.row; break } u = a.end.row } o++ } var f = !1, u = r, a = this.session.getNextFoldLine(u), l = a ? a.start.row : Infinity; for (; ;) { u > l && (u = a.end.row + 1, a = this.session.getNextFoldLine(u, a), l = a ? a.start.row : Infinity); if (u > i) break; var c = s[o++]; if (c) { this.dom.removeChildren(c), this.$renderLine(c, u, u == l ? a : !1), f && (c.style.top = this.$lines.computeLineTop(u, e, this.session) + "px"); var h = e.lineHeight * this.session.getRowLength(u) + "px"; c.style.height != h && (f = !0, c.style.height = h) } u++ } if (f) while (o < this.$lines.cells.length) { var p = this.$lines.cells[o++]; p.element.style.top = this.$lines.computeLineTop(p.row, e, this.session) + "px" } }, this.scrollLines = function (e) { var t = this.config; this.config = e; if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = e.lastRow, r = t ? t.lastRow : -1; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (!t || t.lastRow < e.firstRow) return this.update(e); if (e.lastRow < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (t.lastRow > e.lastRow) for (var i = this.session.getFoldedRowCount(e.lastRow + 1, t.lastRow); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLinesFragment(e, e.firstRow, t.firstRow - 1)), e.lastRow > t.lastRow && this.$lines.push(this.$renderLinesFragment(e, t.lastRow + 1, e.lastRow)) }, this.$renderLinesFragment = function (e, t, n) { var r = [], s = t, o = this.session.getNextFoldLine(s), u = o ? o.start.row : Infinity; for (; ;) { s > u && (s = o.end.row + 1, o = this.session.getNextFoldLine(s, o), u = o ? o.start.row : Infinity); if (s > n) break; var a = this.$lines.createCell(s, e, this.session), f = a.element; this.dom.removeChildren(f), i.setStyle(f.style, "height", this.$lines.computeLineHeight(s, e, this.session) + "px"), i.setStyle(f.style, "top", this.$lines.computeLineTop(s, e, this.session) + "px"), this.$renderLine(f, s, s == u ? o : !1), this.$useLineGroups() ? f.className = "ace_line_group" : f.className = "ace_line", r.push(a), s++ } return r }, this.update = function (e) { this.$lines.moveContainer(e), this.config = e; var t = e.firstRow, n = e.lastRow, r = this.$lines; while (r.getLength()) r.pop(); r.push(this.$renderLinesFragment(e, t, n)) }, this.$textToken = { text: !0, rparen: !0, lparen: !0 }, this.$renderToken = function (e, t, n, r) { var i = this, o = /(\t)|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF\uFFF9-\uFFFC]+)|(\u3000)|([\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g, u = this.dom.createFragment(this.element), a, f = 0; while (a = o.exec(r)) { var l = a[1], c = a[2], h = a[3], p = a[4], d = a[5]; if (!i.showSpaces && c) continue; var v = f != a.index ? r.slice(f, a.index) : ""; f = a.index + a[0].length, v && u.appendChild(this.dom.createTextNode(v, this.element)); if (l) { var m = i.session.getScreenTabSize(t + a.index); u.appendChild(i.$tabStrings[m].cloneNode(!0)), t += m - 1 } else if (c) if (i.showSpaces) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space", g.textContent = s.stringRepeat(i.SPACE_CHAR, c.length), u.appendChild(g) } else u.appendChild(this.com.createTextNode(c, this.element)); else if (h) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space ace_invalid", g.textContent = s.stringRepeat(i.SPACE_CHAR, h.length), u.appendChild(g) } else if (p) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = i.showSpaces ? "ace_cjk ace_invisible ace_invisible_space" : "ace_cjk", g.textContent = i.showSpaces ? i.SPACE_CHAR : p, u.appendChild(g) } else if (d) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = "ace_cjk", g.textContent = d, u.appendChild(g) } } u.appendChild(this.dom.createTextNode(f ? r.slice(f) : r, this.element)); if (!this.$textToken[n.type]) { var y = "ace_" + n.type.replace(/\./g, " ace_"), g = this.dom.createElement("span"); n.type == "fold" && (g.style.width = n.value.length * this.config.characterWidth + "px"), g.className = y, g.appendChild(u), e.appendChild(g) } else e.appendChild(u); return t + r.length }, this.renderIndentGuide = function (e, t, n) { var r = t.search(this.$indentGuideRe); if (r <= 0 || r >= n) return t; if (t[0] == " ") { r -= r % this.tabSize; var i = r / this.tabSize; for (var s = 0; s < i; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } if (t[0] == " ") { for (var s = 0; s < r; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } return t }, this.$createLineElement = function (e) { var t = this.dom.createElement("div"); return t.className = "ace_line", t.style.height = this.config.lineHeight + "px", t }, this.$renderWrappedLine = function (e, t, n) { var r = 0, i = 0, o = n[0], u = 0, a = this.$createLineElement(); e.appendChild(a); for (var f = 0; f < t.length; f++) { var l = t[f], c = l.value; if (f == 0 && this.displayIndentGuides) { r = c.length, c = this.renderIndentGuide(a, c, o); if (!c) continue; r -= c.length } if (r + c.length < o) u = this.$renderToken(a, u, l, c), r += c.length; else { while (r + c.length >= o) u = this.$renderToken(a, u, l, c.substring(0, o - r)), c = c.substring(o - r), r = o, a = this.$createLineElement(), e.appendChild(a), a.appendChild(this.dom.createTextNode(s.stringRepeat("\u00a0", n.indent), this.element)), i++, u = 0, o = n[i] || Number.MAX_VALUE; c.length != 0 && (r += c.length, u = this.$renderToken(a, u, l, c)) } } n[n.length - 1] > this.MAX_LINE_LENGTH && this.$renderOverflowMessage(a, u, null, "", !0) }, this.$renderSimpleLine = function (e, t) { var n = 0, r = t[0], i = r.value; this.displayIndentGuides && (i = this.renderIndentGuide(e, i)), i && (n = this.$renderToken(e, n, r, i)); for (var s = 1; s < t.length; s++) { r = t[s], i = r.value; if (n + i.length > this.MAX_LINE_LENGTH) return this.$renderOverflowMessage(e, n, r, i); n = this.$renderToken(e, n, r, i) } }, this.$renderOverflowMessage = function (e, t, n, r, i) { n && this.$renderToken(e, t, n, r.slice(0, this.MAX_LINE_LENGTH - t)); var s = this.dom.createElement("span"); s.className = "ace_inline_button ace_keyword ace_toggle_wrap", s.textContent = i ? "" : "", e.appendChild(s) }, this.$renderLine = function (e, t, n) { !n && n != 0 && (n = this.session.getFoldLine(t)); if (n) var r = this.$getFoldLineTokens(t, n); else var r = this.session.getTokens(t); var i = e; if (r.length) { var s = this.session.getRowSplitData(t); if (s && s.length) { this.$renderWrappedLine(e, r, s); var i = e.lastChild } else { var i = e; this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)), this.$renderSimpleLine(i, r) } } else this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)); if (this.showEOL && i) { n && (t = n.end.row); var o = this.dom.createElement("span"); o.className = "ace_invisible ace_invisible_eol", o.textContent = t == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR, i.appendChild(o) } }, this.$getFoldLineTokens = function (e, t) { function i(e, t, n) { var i = 0, s = 0; while (s + e[i].value.length < t) { s += e[i].value.length, i++; if (i == e.length) return } if (s != t) { var o = e[i].value.substring(t - s); o.length > n - t && (o = o.substring(0, n - t)), r.push({ type: e[i].type, value: o }), s = t + o.length, i += 1 } while (s < n && i < e.length) { var o = e[i].value; o.length + s > n ? r.push({ type: e[i].type, value: o.substring(0, n - s) }) : r.push(e[i]), s += o.length, i += 1 } } var n = this.session, r = [], s = n.getTokens(e); return t.walk(function (e, t, o, u, a) { e != null ? r.push({ type: "fold", value: e }) : (a && (s = n.getTokens(t)), s.length && i(s, u, o)) }, t.end.row, this.session.getLine(t.end.row).length), r }, this.$useLineGroups = function () { return this.session.getUseWrapMode() }, this.destroy = function () { } }).call(a.prototype), t.Text = a }), define("ace/layer/cursor", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_cursor-layer", e.appendChild(this.element), this.isVisible = !1, this.isBlinking = !0, this.blinkInterval = 1e3, this.smoothBlinking = !1, this.cursors = [], this.cursor = this.addCursor(), r.addCssClass(this.element, "ace_hidden-cursors"), this.$updateCursors = this.$updateOpacity.bind(this) }; (function () { this.$updateOpacity = function (e) { var t = this.cursors; for (var n = t.length; n--;)r.setStyle(t[n].style, "opacity", e ? "" : "0") }, this.$startCssAnimation = function () { var e = this.cursors; for (var t = e.length; t--;)e[t].style.animationDuration = this.blinkInterval + "ms"; setTimeout(function () { r.addCssClass(this.element, "ace_animate-blinking") }.bind(this)) }, this.$stopCssAnimation = function () { r.removeCssClass(this.element, "ace_animate-blinking") }, this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setBlinking = function (e) { e != this.isBlinking && (this.isBlinking = e, this.restartTimer()) }, this.setBlinkInterval = function (e) { e != this.blinkInterval && (this.blinkInterval = e, this.restartTimer()) }, this.setSmoothBlinking = function (e) { e != this.smoothBlinking && (this.smoothBlinking = e, r.setCssClass(this.element, "ace_smooth-blinking", e), this.$updateCursors(!0), this.restartTimer()) }, this.addCursor = function () { var e = r.createElement("div"); return e.className = "ace_cursor", this.element.appendChild(e), this.cursors.push(e), e }, this.removeCursor = function () { if (this.cursors.length > 1) { var e = this.cursors.pop(); return e.parentNode.removeChild(e), e } }, this.hideCursor = function () { this.isVisible = !1, r.addCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.showCursor = function () { this.isVisible = !0, r.removeCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.restartTimer = function () { var e = this.$updateCursors; clearInterval(this.intervalId), clearTimeout(this.timeoutId), this.$stopCssAnimation(), this.smoothBlinking && r.removeCssClass(this.element, "ace_smooth-blinking"), e(!0); if (!this.isBlinking || !this.blinkInterval || !this.isVisible) { this.$stopCssAnimation(); return } this.smoothBlinking && setTimeout(function () { r.addCssClass(this.element, "ace_smooth-blinking") }.bind(this)); if (r.HAS_CSS_ANIMATION) this.$startCssAnimation(); else { var t = function () { this.timeoutId = setTimeout(function () { e(!1) }, .6 * this.blinkInterval) }.bind(this); this.intervalId = setInterval(function () { e(!0), t() }, this.blinkInterval), t() } }, this.getPixelPosition = function (e, t) { if (!this.config || !this.session) return { left: 0, top: 0 }; e || (e = this.session.selection.getCursor()); var n = this.session.documentToScreenPosition(e), r = this.$padding + (this.session.$bidiHandler.isBidiRow(n.row, e.row) ? this.session.$bidiHandler.getPosLeft(n.column) : n.column * this.config.characterWidth), i = (n.row - (t ? this.config.firstRowScreen : 0)) * this.config.lineHeight; return { left: r, top: i } }, this.isCursorInView = function (e, t) { return e.top >= 0 && e.top < t.maxHeight }, this.update = function (e) { this.config = e; var t = this.session.$selectionMarkers, n = 0, i = 0; if (t === undefined || t.length === 0) t = [{ cursor: null }]; for (var n = 0, s = t.length; n < s; n++) { var o = this.getPixelPosition(t[n].cursor, !0); if ((o.top > e.height + e.offset || o.top < 0) && n > 1) continue; var u = this.cursors[i++] || this.addCursor(), a = u.style; this.drawCursor ? this.drawCursor(u, o, e, t[n], this.session) : this.isCursorInView(o, e) ? (r.setStyle(a, "display", "block"), r.translate(u, o.left, o.top), r.setStyle(a, "width", Math.round(e.characterWidth) + "px"), r.setStyle(a, "height", e.lineHeight + "px")) : r.setStyle(a, "display", "none") } while (this.cursors.length > i) this.removeCursor(); var f = this.session.getOverwrite(); this.$setOverwrite(f), this.$pixelPos = o, this.restartTimer() }, this.drawCursor = null, this.$setOverwrite = function (e) { e != this.overwrite && (this.overwrite = e, e ? r.addCssClass(this.element, "ace_overwrite-cursors") : r.removeCssClass(this.element, "ace_overwrite-cursors")) }, this.destroy = function () { clearInterval(this.intervalId), clearTimeout(this.timeoutId) } }).call(i.prototype), t.Cursor = i }), define("ace/scrollbar", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/event", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/event"), o = e("./lib/event_emitter").EventEmitter, u = 32768, a = function (e) { this.element = i.createElement("div"), this.element.className = "ace_scrollbar ace_scrollbar" + this.classSuffix, this.inner = i.createElement("div"), this.inner.className = "ace_scrollbar-inner", this.inner.textContent = "\u00a0", this.element.appendChild(this.inner), e.appendChild(this.element), this.setVisible(!1), this.skipEvent = !1, s.addListener(this.element, "scroll", this.onScroll.bind(this)), s.addListener(this.element, "mousedown", s.preventDefault) }; (function () { r.implement(this, o), this.setVisible = function (e) { this.element.style.display = e ? "" : "none", this.isVisible = e, this.coeff = 1 } }).call(a.prototype); var f = function (e, t) { a.call(this, e), this.scrollTop = 0, this.scrollHeight = 0, t.$scrollbarWidth = this.width = i.scrollbarWidth(e.ownerDocument), this.inner.style.width = this.element.style.width = (this.width || 15) + 5 + "px", this.$minWidth = 0 }; r.inherits(f, a), function () { this.classSuffix = "-v", this.onScroll = function () { if (!this.skipEvent) { this.scrollTop = this.element.scrollTop; if (this.coeff != 1) { var e = this.element.clientHeight / this.scrollHeight; this.scrollTop = this.scrollTop * (1 - e) / (this.coeff - e) } this._emit("scroll", { data: this.scrollTop }) } this.skipEvent = !1 }, this.getWidth = function () { return Math.max(this.isVisible ? this.width : 0, this.$minWidth || 0) }, this.setHeight = function (e) { this.element.style.height = e + "px" }, this.setInnerHeight = this.setScrollHeight = function (e) { this.scrollHeight = e, e > u ? (this.coeff = u / e, e = u) : this.coeff != 1 && (this.coeff = 1), this.inner.style.height = e + "px" }, this.setScrollTop = function (e) { this.scrollTop != e && (this.skipEvent = !0, this.scrollTop = e, this.element.scrollTop = e * this.coeff) } }.call(f.prototype); var l = function (e, t) { a.call(this, e), this.scrollLeft = 0, this.height = t.$scrollbarWidth, this.inner.style.height = this.element.style.height = (this.height || 15) + 5 + "px" }; r.inherits(l, a), function () { this.classSuffix = "-h", this.onScroll = function () { this.skipEvent || (this.scrollLeft = this.element.scrollLeft, this._emit("scroll", { data: this.scrollLeft })), this.skipEvent = !1 }, this.getHeight = function () { return this.isVisible ? this.height : 0 }, this.setWidth = function (e) { this.element.style.width = e + "px" }, this.setInnerWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollLeft = function (e) { this.scrollLeft != e && (this.skipEvent = !0, this.scrollLeft = this.element.scrollLeft = e) } }.call(l.prototype), t.ScrollBar = f, t.ScrollBarV = f, t.ScrollBarH = l, t.VScrollBar = f, t.HScrollBar = l }), define("ace/renderloop", ["require", "exports", "module", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("./lib/event"), i = function (e, t) { this.onRender = e, this.pending = !1, this.changes = 0, this.$recursionLimit = 2, this.window = t || window; var n = this; this._flush = function (e) { n.pending = !1; var t = n.changes; t && (r.blockIdle(100), n.changes = 0, n.onRender(t)); if (n.changes) { if (n.$recursionLimit-- < 0) return; n.schedule() } else n.$recursionLimit = 2 } }; (function () { this.schedule = function (e) { this.changes = this.changes | e, this.changes && !this.pending && (r.nextFrame(this._flush), this.pending = !0) }, this.clear = function (e) { var t = this.changes; return this.changes = 0, t } }).call(i.prototype), t.RenderLoop = i }), define("ace/layer/font_metrics", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/lib/useragent", "ace/lib/event_emitter"], function (e, t, n) { var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("../lib/event"), u = e("../lib/useragent"), a = e("../lib/event_emitter").EventEmitter, f = 256, l = typeof ResizeObserver == "function", c = 200, h = t.FontMetrics = function (e) { this.el = i.createElement("div"), this.$setMeasureNodeStyles(this.el.style, !0), this.$main = i.createElement("div"), this.$setMeasureNodeStyles(this.$main.style), this.$measureNode = i.createElement("div"), this.$setMeasureNodeStyles(this.$measureNode.style), this.el.appendChild(this.$main), this.el.appendChild(this.$measureNode), e.appendChild(this.el), this.$measureNode.textContent = s.stringRepeat("X", f), this.$characterSize = { width: 0, height: 0 }, l ? this.$addObserver() : this.checkForSizeChanges() }; (function () { r.implement(this, a), this.$characterSize = { width: 0, height: 0 }, this.$setMeasureNodeStyles = function (e, t) { e.width = e.height = "auto", e.left = e.top = "0px", e.visibility = "hidden", e.position = "absolute", e.whiteSpace = "pre", u.isIE < 8 ? e["font-family"] = "inherit" : e.font = "inherit", e.overflow = t ? "hidden" : "visible" }, this.checkForSizeChanges = function (e) { e === undefined && (e = this.$measureSizes()); if (e && (this.$characterSize.width !== e.width || this.$characterSize.height !== e.height)) { this.$measureNode.style.fontWeight = "bold"; var t = this.$measureSizes(); this.$measureNode.style.fontWeight = "", this.$characterSize = e, this.charSizes = Object.create(null), this.allowBoldFonts = t && t.width === e.width && t.height === e.height, this._emit("changeCharacterSize", { data: e }) } }, this.$addObserver = function () { var e = this; this.$observer = new window.ResizeObserver(function (t) { e.checkForSizeChanges() }), this.$observer.observe(this.$measureNode) }, this.$pollSizeChanges = function () { if (this.$pollSizeChangesTimer || this.$observer) return this.$pollSizeChangesTimer; var e = this; return this.$pollSizeChangesTimer = o.onIdle(function t() { e.checkForSizeChanges(), o.onIdle(t, 500) }, 500) }, this.setPolling = function (e) { e ? this.$pollSizeChanges() : this.$pollSizeChangesTimer && (clearInterval(this.$pollSizeChangesTimer), this.$pollSizeChangesTimer = 0) }, this.$measureSizes = function (e) { var t = { height: (e || this.$measureNode).clientHeight, width: (e || this.$measureNode).clientWidth / f }; return t.width === 0 || t.height === 0 ? null : t }, this.$measureCharWidth = function (e) { this.$main.textContent = s.stringRepeat(e, f); var t = this.$main.getBoundingClientRect(); return t.width / f }, this.getCharacterWidth = function (e) { var t = this.charSizes[e]; return t === undefined && (t = this.charSizes[e] = this.$measureCharWidth(e) / this.$characterSize.width), t }, this.destroy = function () { clearInterval(this.$pollSizeChangesTimer), this.$observer && this.$observer.disconnect(), this.el && this.el.parentNode && this.el.parentNode.removeChild(this.el) }, this.$getZoom = function e(t) { return t ? (window.getComputedStyle(t).zoom || 1) * e(t.parentElement) : 1 }, this.$initTransformMeasureNodes = function () { var e = function (e, t) { return ["div", { style: "position: absolute;top:" + e + "px;left:" + t + "px;" }] }; this.els = i.buildDom([e(0, 0), e(c, 0), e(0, c), e(c, c)], this.el) }, this.transformCoordinates = function (e, t) { function r(e, t, n) { var r = e[1] * t[0] - e[0] * t[1]; return [(-t[1] * n[0] + t[0] * n[1]) / r, (+e[1] * n[0] - e[0] * n[1]) / r] } function i(e, t) { return [e[0] - t[0], e[1] - t[1]] } function s(e, t) { return [e[0] + t[0], e[1] + t[1]] } function o(e, t) { return [e * t[0], e * t[1]] } function u(e) { var t = e.getBoundingClientRect(); return [t.left, t.top] } if (e) { var n = this.$getZoom(this.el); e = o(1 / n, e) } this.els || this.$initTransformMeasureNodes(); var a = u(this.els[0]), f = u(this.els[1]), l = u(this.els[2]), h = u(this.els[3]), p = r(i(h, f), i(h, l), i(s(f, l), s(h, a))), d = o(1 + p[0], i(f, a)), v = o(1 + p[1], i(l, a)); if (t) { var m = t, g = p[0] * m[0] / c + p[1] * m[1] / c + 1, y = s(o(m[0], d), o(m[1], v)); return s(o(1 / g / c, y), a) } var b = i(e, a), w = r(i(d, o(p[0], b)), i(v, o(p[1], b)), b); return o(c, w) } }).call(h.prototype) }), define("ace/virtual_renderer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/config", "ace/layer/gutter", "ace/layer/marker", "ace/layer/text", "ace/layer/cursor", "ace/scrollbar", "ace/scrollbar", "ace/renderloop", "ace/layer/font_metrics", "ace/lib/event_emitter", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./config"), o = e("./layer/gutter").Gutter, u = e("./layer/marker").Marker, a = e("./layer/text").Text, f = e("./layer/cursor").Cursor, l = e("./scrollbar").HScrollBar, c = e("./scrollbar").VScrollBar, h = e("./renderloop").RenderLoop, p = e("./layer/font_metrics").FontMetrics, d = e("./lib/event_emitter").EventEmitter, v = '.ace_br1 {border-top-left-radius : 3px;}.ace_br2 {border-top-right-radius : 3px;}.ace_br3 {border-top-left-radius : 3px; border-top-right-radius: 3px;}.ace_br4 {border-bottom-right-radius: 3px;}.ace_br5 {border-top-left-radius : 3px; border-bottom-right-radius: 3px;}.ace_br6 {border-top-right-radius : 3px; border-bottom-right-radius: 3px;}.ace_br7 {border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px;}.ace_br8 {border-bottom-left-radius : 3px;}.ace_br9 {border-top-left-radius : 3px; border-bottom-left-radius: 3px;}.ace_br10{border-top-right-radius : 3px; border-bottom-left-radius: 3px;}.ace_br11{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br12{border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br13{border-top-left-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br14{border-top-right-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br15{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_editor {position: relative;overflow: hidden;padding: 0;font: 12px/normal \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'source-code-pro\', monospace;direction: ltr;text-align: left;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}.ace_scroller {position: absolute;overflow: hidden;top: 0;bottom: 0;background-color: inherit;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;cursor: text;}.ace_content {position: absolute;box-sizing: border-box;min-width: 100%;contain: style size layout;font-variant-ligatures: no-common-ligatures;}.ace_dragging .ace_scroller:before{position: absolute;top: 0;left: 0;right: 0;bottom: 0;content: \'\';background: rgba(250, 250, 250, 0.01);z-index: 1000;}.ace_dragging.ace_dark .ace_scroller:before{background: rgba(0, 0, 0, 0.01);}.ace_selecting, .ace_selecting * {cursor: text !important;}.ace_gutter {position: absolute;overflow : hidden;width: auto;top: 0;bottom: 0;left: 0;cursor: default;z-index: 4;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;contain: style size layout;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {position: absolute;top: 0;left: 0;right: 0;padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");}.ace_scrollbar {contain: strict;position: absolute;right: 0;bottom: 0;z-index: 6;}.ace_scrollbar-inner {position: absolute;cursor: text;left: 0;top: 0;}.ace_scrollbar-v{overflow-x: hidden;overflow-y: scroll;top: 0;}.ace_scrollbar-h {overflow-x: scroll;overflow-y: hidden;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;font: inherit;padding: 0 1px;margin: 0 -1px;contain: strict;-ms-user-select: text;-moz-user-select: text;-webkit-user-select: text;user-select: text;white-space: pre!important;}.ace_text-input.ace_composition {background: transparent;color: inherit;z-index: 1000;opacity: 1;}.ace_composition_placeholder { color: transparent }.ace_composition_marker { border-bottom: 1px solid;position: absolute;border-radius: 0;margin-top: 1px;}[ace_nocontext=true] {transform: none!important;filter: none!important;clip-path: none!important;mask : none!important;contain: none!important;perspective: none!important;mix-blend-mode: initial!important;z-index: auto;}.ace_layer {z-index: 1;position: absolute;overflow: hidden;word-wrap: normal;white-space: pre;height: 100%;width: 100%;box-sizing: border-box;pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;height: 1000000px;contain: style size layout;}.ace_text-layer {font: inherit !important;position: absolute;height: 1000000px;width: 1000000px;contain: style size layout;}.ace_text-layer > .ace_line, .ace_text-layer > .ace_line_group {contain: style size layout;position: absolute;top: 0;left: 0;right: 0;}.ace_hidpi .ace_text-layer,.ace_hidpi .ace_gutter-layer,.ace_hidpi .ace_content,.ace_hidpi .ace_gutter {contain: strict;will-change: transform;}.ace_hidpi .ace_text-layer > .ace_line, .ace_hidpi .ace_text-layer > .ace_line_group {contain: strict;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;box-sizing: border-box;border-left: 2px solid;transform: translatez(0);}.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_slim-cursors .ace_cursor {border-left-width: 1px;}.ace_overwrite-cursors .ace_cursor {border-left-width: 0;border-bottom: 1px solid;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_hasPlaceholder .ace_hidden-cursors .ace_cursor {opacity: 0;}.ace_smooth-blinking .ace_cursor {transition: opacity 0.18s;}.ace_animate-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: step-end;animation-name: blink-ace-animate;animation-iteration-count: infinite;}.ace_animate-blinking.ace_smooth-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: ease-in-out;animation-name: blink-ace-animate-smooth;}@keyframes blink-ace-animate {from, to { opacity: 1; }60% { opacity: 0; }}@keyframes blink-ace-animate-smooth {from, to { opacity: 1; }45% { opacity: 1; }60% { opacity: 0; }85% { opacity: 0; }}.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_error_bracket {position: absolute;border-bottom: 1px solid #DE5555;border-radius: 0;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;box-sizing: border-box;}.ace_line .ace_fold {box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");}.ace_tooltip {background-color: #FFF;background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));border: 1px solid gray;border-radius: 1px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);color: black;max-width: 100%;padding: 3px 4px;position: fixed;z-index: 999999;box-sizing: border-box;cursor: default;white-space: pre;word-wrap: break-word;line-height: normal;font-style: normal;font-weight: normal;letter-spacing: normal;pointer-events: none;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {box-sizing: border-box;margin: 0 -12px 0 1px;display: none;width: 11px;vertical-align: top;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;cursor: pointer;}.ace_folding-enabled .ace_fold-widget {display: inline-block; }.ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");}.ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}.ace_dark .ace_fold-widget {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");}.ace_dark .ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_inline_button {border: 1px solid lightgray;display: inline-block;margin: -1px 8px;padding: 0 5px;pointer-events: auto;cursor: pointer;}.ace_inline_button:hover {border-color: gray;background: rgba(200,200,200,0.2);display: inline-block;pointer-events: auto;}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}.ace_error-marker {background-color: rgba(255, 0, 0,0.2);position: absolute;z-index: 9;}.ace_highlight-marker {background-color: rgba(255, 255, 0,0.2);position: absolute;z-index: 8;}.ace_mobile-menu {position: absolute;line-height: 1.5;border-radius: 4px;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;background: white;box-shadow: 1px 3px 2px grey;border: 1px solid #dcdcdc;color: black;}.ace_dark > .ace_mobile-menu {background: #333;color: #ccc;box-shadow: 1px 3px 2px grey;border: 1px solid #444;}.ace_mobile-button {padding: 2px;cursor: pointer;overflow: hidden;}.ace_mobile-button:hover {background-color: #eee;opacity:1;}.ace_mobile-button:active {background-color: #ddd;}.ace_placeholder {font-family: arial;transform: scale(0.9);transform-origin: left;white-space: pre;opacity: 0.7;margin: 0 10px;}', m = e("./lib/useragent"), g = m.isIE; i.importCssString(v, "ace_editor.css"); var y = function (e, t) { var n = this; this.container = e || i.createElement("div"), i.addCssClass(this.container, "ace_editor"), i.HI_DPI && i.addCssClass(this.container, "ace_hidpi"), this.setTheme(t), this.$gutter = i.createElement("div"), this.$gutter.className = "ace_gutter", this.container.appendChild(this.$gutter), this.$gutter.setAttribute("aria-hidden", !0), this.scroller = i.createElement("div"), this.scroller.className = "ace_scroller", this.container.appendChild(this.scroller), this.content = i.createElement("div"), this.content.className = "ace_content", this.scroller.appendChild(this.content), this.$gutterLayer = new o(this.$gutter), this.$gutterLayer.on("changeGutterWidth", this.onGutterResize.bind(this)), this.$markerBack = new u(this.content); var r = this.$textLayer = new a(this.content); this.canvas = r.element, this.$markerFront = new u(this.content), this.$cursorLayer = new f(this.content), this.$horizScroll = !1, this.$vScroll = !1, this.scrollBar = this.scrollBarV = new c(this.container, this), this.scrollBarH = new l(this.container, this), this.scrollBarV.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollTop(e.data - n.scrollMargin.top) }), this.scrollBarH.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollLeft(e.data - n.scrollMargin.left) }), this.scrollTop = 0, this.scrollLeft = 0, this.cursorPos = { row: 0, column: 0 }, this.$fontMetrics = new p(this.container), this.$textLayer.$setFontMetrics(this.$fontMetrics), this.$textLayer.on("changeCharacterSize", function (e) { n.updateCharacterSize(), n.onResize(!0, n.gutterWidth, n.$size.width, n.$size.height), n._signal("changeCharacterSize", e) }), this.$size = { width: 0, height: 0, scrollerHeight: 0, scrollerWidth: 0, $dirty: !0 }, this.layerConfig = { width: 1, padding: 0, firstRow: 0, firstRowScreen: 0, lastRow: 0, lineHeight: 0, characterWidth: 0, minHeight: 1, maxHeight: 1, offset: 0, height: 1, gutterOffset: 1 }, this.scrollMargin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.margin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.$keepTextAreaAtCursor = !m.isIOS, this.$loop = new h(this.$renderChanges.bind(this), this.container.ownerDocument.defaultView), this.$loop.schedule(this.CHANGE_FULL), this.updateCharacterSize(), this.setPadding(4), s.resetOptions(this), s._signal("renderer", this) }; (function () { this.CHANGE_CURSOR = 1, this.CHANGE_MARKER = 2, this.CHANGE_GUTTER = 4, this.CHANGE_SCROLL = 8, this.CHANGE_LINES = 16, this.CHANGE_TEXT = 32, this.CHANGE_SIZE = 64, this.CHANGE_MARKER_BACK = 128, this.CHANGE_MARKER_FRONT = 256, this.CHANGE_FULL = 512, this.CHANGE_H_SCROLL = 1024, r.implement(this, d), this.updateCharacterSize = function () { this.$textLayer.allowBoldFonts != this.$allowBoldFonts && (this.$allowBoldFonts = this.$textLayer.allowBoldFonts, this.setStyle("ace_nobold", !this.$allowBoldFonts)), this.layerConfig.characterWidth = this.characterWidth = this.$textLayer.getCharacterWidth(), this.layerConfig.lineHeight = this.lineHeight = this.$textLayer.getLineHeight(), this.$updatePrintMargin(), i.setStyle(this.scroller.style, "line-height", this.lineHeight + "px") }, this.setSession = function (e) { this.session && this.session.doc.off("changeNewLineMode", this.onChangeNewLineMode), this.session = e, e && this.scrollMargin.top && e.getScrollTop() <= 0 && e.setScrollTop(-this.scrollMargin.top), this.$cursorLayer.setSession(e), this.$markerBack.setSession(e), this.$markerFront.setSession(e), this.$gutterLayer.setSession(e), this.$textLayer.setSession(e); if (!e) return; this.$loop.schedule(this.CHANGE_FULL), this.session.$setFontMetrics(this.$fontMetrics), this.scrollBarH.scrollLeft = this.scrollBarV.scrollTop = null, this.onChangeNewLineMode = this.onChangeNewLineMode.bind(this), this.onChangeNewLineMode(), this.session.doc.on("changeNewLineMode", this.onChangeNewLineMode) }, this.updateLines = function (e, t, n) { t === undefined && (t = Infinity), this.$changedLines ? (this.$changedLines.firstRow > e && (this.$changedLines.firstRow = e), this.$changedLines.lastRow < t && (this.$changedLines.lastRow = t)) : this.$changedLines = { firstRow: e, lastRow: t }; if (this.$changedLines.lastRow < this.layerConfig.firstRow) { if (!n) return; this.$changedLines.lastRow = this.layerConfig.lastRow } if (this.$changedLines.firstRow > this.layerConfig.lastRow) return; this.$loop.schedule(this.CHANGE_LINES) }, this.onChangeNewLineMode = function () { this.$loop.schedule(this.CHANGE_TEXT), this.$textLayer.$updateEolChar(), this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR) }, this.onChangeTabSize = function () { this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER), this.$textLayer.onChangeTabSize() }, this.updateText = function () { this.$loop.schedule(this.CHANGE_TEXT) }, this.updateFull = function (e) { e ? this.$renderChanges(this.CHANGE_FULL, !0) : this.$loop.schedule(this.CHANGE_FULL) }, this.updateFontSize = function () { this.$textLayer.checkForSizeChanges() }, this.$changes = 0, this.$updateSizeAsync = function () { this.$loop.pending ? this.$size.$dirty = !0 : this.onResize() }, this.onResize = function (e, t, n, r) { if (this.resizing > 2) return; this.resizing > 0 ? this.resizing++ : this.resizing = e ? 1 : 0; var i = this.container; r || (r = i.clientHeight || i.scrollHeight), n || (n = i.clientWidth || i.scrollWidth); var s = this.$updateCachedSize(e, t, n, r); if (!this.$size.scrollerHeight || !n && !r) return this.resizing = 0; e && (this.$gutterLayer.$padding = null), e ? this.$renderChanges(s | this.$changes, !0) : this.$loop.schedule(s | this.$changes), this.resizing && (this.resizing = 0), this.scrollBarV.scrollLeft = this.scrollBarV.scrollTop = null }, this.$updateCachedSize = function (e, t, n, r) { r -= this.$extraHeight || 0; var s = 0, o = this.$size, u = { width: o.width, height: o.height, scrollerHeight: o.scrollerHeight, scrollerWidth: o.scrollerWidth }; r && (e || o.height != r) && (o.height = r, s |= this.CHANGE_SIZE, o.scrollerHeight = o.height, this.$horizScroll && (o.scrollerHeight -= this.scrollBarH.getHeight()), this.scrollBarV.element.style.bottom = this.scrollBarH.getHeight() + "px", s |= this.CHANGE_SCROLL); if (n && (e || o.width != n)) { s |= this.CHANGE_SIZE, o.width = n, t == null && (t = this.$showGutter ? this.$gutter.offsetWidth : 0), this.gutterWidth = t, i.setStyle(this.scrollBarH.element.style, "left", t + "px"), i.setStyle(this.scroller.style, "left", t + this.margin.left + "px"), o.scrollerWidth = Math.max(0, n - t - this.scrollBarV.getWidth() - this.margin.h), i.setStyle(this.$gutter.style, "left", this.margin.left + "px"); var a = this.scrollBarV.getWidth() + "px"; i.setStyle(this.scrollBarH.element.style, "right", a), i.setStyle(this.scroller.style, "right", a), i.setStyle(this.scroller.style, "bottom", this.scrollBarH.getHeight()); if (this.session && this.session.getUseWrapMode() && this.adjustWrapLimit() || e) s |= this.CHANGE_FULL } return o.$dirty = !n || !r, s && this._signal("resize", u), s }, this.onGutterResize = function (e) { var t = this.$showGutter ? e : 0; t != this.gutterWidth && (this.$changes |= this.$updateCachedSize(!0, t, this.$size.width, this.$size.height)), this.session.getUseWrapMode() && this.adjustWrapLimit() ? this.$loop.schedule(this.CHANGE_FULL) : this.$size.$dirty ? this.$loop.schedule(this.CHANGE_FULL) : this.$computeLayerConfig() }, this.adjustWrapLimit = function () { var e = this.$size.scrollerWidth - this.$padding * 2, t = Math.floor(e / this.characterWidth); return this.session.adjustWrapLimit(t, this.$showPrintMargin && this.$printMarginColumn) }, this.setAnimatedScroll = function (e) { this.setOption("animatedScroll", e) }, this.getAnimatedScroll = function () { return this.$animatedScroll }, this.setShowInvisibles = function (e) { this.setOption("showInvisibles", e), this.session.$bidiHandler.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.getOption("showInvisibles") }, this.getDisplayIndentGuides = function () { return this.getOption("displayIndentGuides") }, this.setDisplayIndentGuides = function (e) { this.setOption("displayIndentGuides", e) }, this.setShowPrintMargin = function (e) { this.setOption("showPrintMargin", e) }, this.getShowPrintMargin = function () { return this.getOption("showPrintMargin") }, this.setPrintMarginColumn = function (e) { this.setOption("printMarginColumn", e) }, this.getPrintMarginColumn = function () { return this.getOption("printMarginColumn") }, this.getShowGutter = function () { return this.getOption("showGutter") }, this.setShowGutter = function (e) { return this.setOption("showGutter", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.$updatePrintMargin = function () { if (!this.$showPrintMargin && !this.$printMarginEl) return; if (!this.$printMarginEl) { var e = i.createElement("div"); e.className = "ace_layer ace_print-margin-layer", this.$printMarginEl = i.createElement("div"), this.$printMarginEl.className = "ace_print-margin", e.appendChild(this.$printMarginEl), this.content.insertBefore(e, this.content.firstChild) } var t = this.$printMarginEl.style; t.left = Math.round(this.characterWidth * this.$printMarginColumn + this.$padding) + "px", t.visibility = this.$showPrintMargin ? "visible" : "hidden", this.session && this.session.$wrap == -1 && this.adjustWrapLimit() }, this.getContainerElement = function () { return this.container }, this.getMouseEventTarget = function () { return this.scroller }, this.getTextAreaContainer = function () { return this.container }, this.$moveTextAreaToCursor = function () { if (this.$isMousePressed) return; var e = this.textarea.style, t = this.$composition; if (!this.$keepTextAreaAtCursor && !t) { i.translate(this.textarea, -100, 0); return } var n = this.$cursorLayer.$pixelPos; if (!n) return; t && t.markerRange && (n = this.$cursorLayer.getPixelPosition(t.markerRange.start, !0)); var r = this.layerConfig, s = n.top, o = n.left; s -= r.offset; var u = t && t.useTextareaForIME ? this.lineHeight : g ? 0 : 1; if (s < 0 || s > r.height - u) { i.translate(this.textarea, 0, 0); return } var a = 1, f = this.$size.height - u; if (!t) s += this.lineHeight; else if (t.useTextareaForIME) { var l = this.textarea.value; a = this.characterWidth * this.session.$getStringScreenWidth(l)[0] } else s += this.lineHeight + 2; o -= this.scrollLeft, o > this.$size.scrollerWidth - a && (o = this.$size.scrollerWidth - a), o += this.gutterWidth + this.margin.left, i.setStyle(e, "height", u + "px"), i.setStyle(e, "width", a + "px"), i.translate(this.textarea, Math.min(o, this.$size.scrollerWidth - a), Math.min(s, f)) }, this.getFirstVisibleRow = function () { return this.layerConfig.firstRow }, this.getFirstFullyVisibleRow = function () { return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1) }, this.getLastFullyVisibleRow = function () { var e = this.layerConfig, t = e.lastRow, n = this.session.documentToScreenRow(t, 0) * e.lineHeight; return n - this.session.getScrollTop() > e.height - e.lineHeight ? t - 1 : t }, this.getLastVisibleRow = function () { return this.layerConfig.lastRow }, this.$padding = null, this.setPadding = function (e) { this.$padding = e, this.$textLayer.setPadding(e), this.$cursorLayer.setPadding(e), this.$markerFront.setPadding(e), this.$markerBack.setPadding(e), this.$loop.schedule(this.CHANGE_FULL), this.$updatePrintMargin() }, this.setScrollMargin = function (e, t, n, r) { var i = this.scrollMargin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, i.top && this.scrollTop <= 0 && this.session && this.session.setScrollTop(-i.top), this.updateFull() }, this.setMargin = function (e, t, n, r) { var i = this.margin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, this.$updateCachedSize(!0, this.gutterWidth, this.$size.width, this.$size.height), this.updateFull() }, this.getHScrollBarAlwaysVisible = function () { return this.$hScrollBarAlwaysVisible }, this.setHScrollBarAlwaysVisible = function (e) { this.setOption("hScrollBarAlwaysVisible", e) }, this.getVScrollBarAlwaysVisible = function () { return this.$vScrollBarAlwaysVisible }, this.setVScrollBarAlwaysVisible = function (e) { this.setOption("vScrollBarAlwaysVisible", e) }, this.$updateScrollBarV = function () { var e = this.layerConfig.maxHeight, t = this.$size.scrollerHeight; !this.$maxLines && this.$scrollPastEnd && (e -= (t - this.lineHeight) * this.$scrollPastEnd, this.scrollTop > e - t && (e = this.scrollTop + t, this.scrollBarV.scrollTop = null)), this.scrollBarV.setScrollHeight(e + this.scrollMargin.v), this.scrollBarV.setScrollTop(this.scrollTop + this.scrollMargin.top) }, this.$updateScrollBarH = function () { this.scrollBarH.setScrollWidth(this.layerConfig.width + 2 * this.$padding + this.scrollMargin.h), this.scrollBarH.setScrollLeft(this.scrollLeft + this.scrollMargin.left) }, this.$frozen = !1, this.freeze = function () { this.$frozen = !0 }, this.unfreeze = function () { this.$frozen = !1 }, this.$renderChanges = function (e, t) { this.$changes && (e |= this.$changes, this.$changes = 0); if (!this.session || !this.container.offsetWidth || this.$frozen || !e && !t) { this.$changes |= e; return } if (this.$size.$dirty) return this.$changes |= e, this.onResize(!0); this.lineHeight || this.$textLayer.checkForSizeChanges(), this._signal("beforeRender", e), this.session && this.session.$bidiHandler && this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics); var n = this.layerConfig; if (e & this.CHANGE_FULL || e & this.CHANGE_SIZE || e & this.CHANGE_TEXT || e & this.CHANGE_LINES || e & this.CHANGE_SCROLL || e & this.CHANGE_H_SCROLL) { e |= this.$computeLayerConfig() | this.$loop.clear(); if (n.firstRow != this.layerConfig.firstRow && n.firstRowScreen == this.layerConfig.firstRowScreen) { var r = this.scrollTop + (n.firstRow - this.layerConfig.firstRow) * this.lineHeight; r > 0 && (this.scrollTop = r, e |= this.CHANGE_SCROLL, e |= this.$computeLayerConfig() | this.$loop.clear()) } n = this.layerConfig, this.$updateScrollBarV(), e & this.CHANGE_H_SCROLL && this.$updateScrollBarH(), i.translate(this.content, -this.scrollLeft, -n.offset); var s = n.width + 2 * this.$padding + "px", o = n.minHeight + "px"; i.setStyle(this.content.style, "width", s), i.setStyle(this.content.style, "height", o) } e & this.CHANGE_H_SCROLL && (i.translate(this.content, -this.scrollLeft, -n.offset), this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left"); if (e & this.CHANGE_FULL) { this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } if (e & this.CHANGE_SCROLL) { this.$changedLines = null, e & this.CHANGE_TEXT || e & this.CHANGE_LINES ? this.$textLayer.update(n) : this.$textLayer.scrollLines(n), this.$showGutter && (e & this.CHANGE_GUTTER || e & this.CHANGE_LINES ? this.$gutterLayer.update(n) : this.$gutterLayer.scrollLines(n)), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } e & this.CHANGE_TEXT ? (this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n)) : e & this.CHANGE_LINES ? (this.$updateLines() || e & this.CHANGE_GUTTER && this.$showGutter) && this.$gutterLayer.update(n) : e & this.CHANGE_TEXT || e & this.CHANGE_GUTTER ? this.$showGutter && this.$gutterLayer.update(n) : e & this.CHANGE_CURSOR && this.$highlightGutterLine && this.$gutterLayer.updateLineHighlight(n), e & this.CHANGE_CURSOR && (this.$cursorLayer.update(n), this.$moveTextAreaToCursor()), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT) && this.$markerFront.update(n), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK) && this.$markerBack.update(n), this._signal("afterRender", e) }, this.$autosize = function () { var e = this.session.getScreenLength() * this.lineHeight, t = this.$maxLines * this.lineHeight, n = Math.min(t, Math.max((this.$minLines || 1) * this.lineHeight, e)) + this.scrollMargin.v + (this.$extraHeight || 0); this.$horizScroll && (n += this.scrollBarH.getHeight()), this.$maxPixelHeight && n > this.$maxPixelHeight && (n = this.$maxPixelHeight); var r = n <= 2 * this.lineHeight, i = !r && e > t; if (n != this.desiredHeight || this.$size.height != this.desiredHeight || i != this.$vScroll) { i != this.$vScroll && (this.$vScroll = i, this.scrollBarV.setVisible(i)); var s = this.container.clientWidth; this.container.style.height = n + "px", this.$updateCachedSize(!0, this.$gutterWidth, s, n), this.desiredHeight = n, this._signal("autosize") } }, this.$computeLayerConfig = function () { var e = this.session, t = this.$size, n = t.height <= 2 * this.lineHeight, r = this.session.getScreenLength(), i = r * this.lineHeight, s = this.$getLongestLine(), o = !n && (this.$hScrollBarAlwaysVisible || t.scrollerWidth - s - 2 * this.$padding < 0), u = this.$horizScroll !== o; u && (this.$horizScroll = o, this.scrollBarH.setVisible(o)); var a = this.$vScroll; this.$maxLines && this.lineHeight > 1 && this.$autosize(); var f = t.scrollerHeight + this.lineHeight, l = !this.$maxLines && this.$scrollPastEnd ? (t.scrollerHeight - this.lineHeight) * this.$scrollPastEnd : 0; i += l; var c = this.scrollMargin; this.session.setScrollTop(Math.max(-c.top, Math.min(this.scrollTop, i - t.scrollerHeight + c.bottom))), this.session.setScrollLeft(Math.max(-c.left, Math.min(this.scrollLeft, s + 2 * this.$padding - t.scrollerWidth + c.right))); var h = !n && (this.$vScrollBarAlwaysVisible || t.scrollerHeight - i + l < 0 || this.scrollTop > c.top), p = a !== h; p && (this.$vScroll = h, this.scrollBarV.setVisible(h)); var d = this.scrollTop % this.lineHeight, v = Math.ceil(f / this.lineHeight) - 1, m = Math.max(0, Math.round((this.scrollTop - d) / this.lineHeight)), g = m + v, y, b, w = this.lineHeight; m = e.screenToDocumentRow(m, 0); var E = e.getFoldLine(m); E && (m = E.start.row), y = e.documentToScreenRow(m, 0), b = e.getRowLength(m) * w, g = Math.min(e.screenToDocumentRow(g, 0), e.getLength() - 1), f = t.scrollerHeight + e.getRowLength(g) * w + b, d = this.scrollTop - y * w; var S = 0; if (this.layerConfig.width != s || u) S = this.CHANGE_H_SCROLL; if (u || p) S |= this.$updateCachedSize(!0, this.gutterWidth, t.width, t.height), this._signal("scrollbarVisibilityChanged"), p && (s = this.$getLongestLine()); return this.layerConfig = { width: s, padding: this.$padding, firstRow: m, firstRowScreen: y, lastRow: g, lineHeight: w, characterWidth: this.characterWidth, minHeight: f, maxHeight: i, offset: d, gutterOffset: w ? Math.max(0, Math.ceil((d + t.height - t.scrollerHeight) / w)) : 0, height: this.$size.scrollerHeight }, this.session.$bidiHandler && this.session.$bidiHandler.setContentWidth(s - this.$padding), S }, this.$updateLines = function () { if (!this.$changedLines) return; var e = this.$changedLines.firstRow, t = this.$changedLines.lastRow; this.$changedLines = null; var n = this.layerConfig; if (e > n.lastRow + 1) return; if (t < n.firstRow) return; if (t === Infinity) { this.$showGutter && this.$gutterLayer.update(n), this.$textLayer.update(n); return } return this.$textLayer.updateLines(n, e, t), !0 }, this.$getLongestLine = function () { var e = this.session.getScreenWidth(); return this.showInvisibles && !this.session.$useWrapMode && (e += 1), this.$textLayer && e > this.$textLayer.MAX_LINE_LENGTH && (e = this.$textLayer.MAX_LINE_LENGTH + 30), Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(e * this.characterWidth)) }, this.updateFrontMarkers = function () { this.$markerFront.setMarkers(this.session.getMarkers(!0)), this.$loop.schedule(this.CHANGE_MARKER_FRONT) }, this.updateBackMarkers = function () { this.$markerBack.setMarkers(this.session.getMarkers()), this.$loop.schedule(this.CHANGE_MARKER_BACK) }, this.addGutterDecoration = function (e, t) { this.$gutterLayer.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { this.$gutterLayer.removeGutterDecoration(e, t) }, this.updateBreakpoints = function (e) { this.$loop.schedule(this.CHANGE_GUTTER) }, this.setAnnotations = function (e) { this.$gutterLayer.setAnnotations(e), this.$loop.schedule(this.CHANGE_GUTTER) }, this.updateCursor = function () { this.$loop.schedule(this.CHANGE_CURSOR) }, this.hideCursor = function () { this.$cursorLayer.hideCursor() }, this.showCursor = function () { this.$cursorLayer.showCursor() }, this.scrollSelectionIntoView = function (e, t, n) { this.scrollCursorIntoView(e, n), this.scrollCursorIntoView(t, n) }, this.scrollCursorIntoView = function (e, t, n) { if (this.$size.scrollerHeight === 0) return; var r = this.$cursorLayer.getPixelPosition(e), i = r.left, s = r.top, o = n && n.top || 0, u = n && n.bottom || 0, a = this.$scrollAnimation ? this.session.getScrollTop() : this.scrollTop; a + o > s ? (t && a + o > s + this.lineHeight && (s -= t * this.$size.scrollerHeight), s === 0 && (s = -this.scrollMargin.top), this.session.setScrollTop(s)) : a + this.$size.scrollerHeight - u < s + this.lineHeight && (t && a + this.$size.scrollerHeight - u < s - this.lineHeight && (s += t * this.$size.scrollerHeight), this.session.setScrollTop(s + this.lineHeight + u - this.$size.scrollerHeight)); var f = this.scrollLeft; f > i ? (i < this.$padding + 2 * this.layerConfig.characterWidth && (i = -this.scrollMargin.left), this.session.setScrollLeft(i)) : f + this.$size.scrollerWidth < i + this.characterWidth ? this.session.setScrollLeft(Math.round(i + this.characterWidth - this.$size.scrollerWidth)) : f <= this.$padding && i - f < this.characterWidth && this.session.setScrollLeft(0) }, this.getScrollTop = function () { return this.session.getScrollTop() }, this.getScrollLeft = function () { return this.session.getScrollLeft() }, this.getScrollTopRow = function () { return this.scrollTop / this.lineHeight }, this.getScrollBottomRow = function () { return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1) }, this.scrollToRow = function (e) { this.session.setScrollTop(e * this.lineHeight) }, this.alignCursor = function (e, t) { typeof e == "number" && (e = { row: e, column: 0 }); var n = this.$cursorLayer.getPixelPosition(e), r = this.$size.scrollerHeight - this.lineHeight, i = n.top - r * (t || 0); return this.session.setScrollTop(i), i }, this.STEPS = 8, this.$calcSteps = function (e, t) { var n = 0, r = this.STEPS, i = [], s = function (e, t, n) { return n * (Math.pow(e - 1, 3) + 1) + t }; for (n = 0; n < r; ++n)i.push(s(n / this.STEPS, e, t - e)); return i }, this.scrollToLine = function (e, t, n, r) { var i = this.$cursorLayer.getPixelPosition({ row: e, column: 0 }), s = i.top; t && (s -= this.$size.scrollerHeight / 2); var o = this.scrollTop; this.session.setScrollTop(s), n !== !1 && this.animateScrolling(o, r) }, this.animateScrolling = function (e, t) { var n = this.scrollTop; if (!this.$animatedScroll) return; var r = this; if (e == n) return; if (this.$scrollAnimation) { var i = this.$scrollAnimation.steps; if (i.length) { e = i[0]; if (e == n) return } } var s = r.$calcSteps(e, n); this.$scrollAnimation = { from: e, to: n, steps: s }, clearInterval(this.$timer), r.session.setScrollTop(s.shift()), r.session.$scrollTop = n, this.$timer = setInterval(function () { if (!r.session) return clearInterval(r.$timer); s.length ? (r.session.setScrollTop(s.shift()), r.session.$scrollTop = n) : n != null ? (r.session.$scrollTop = -1, r.session.setScrollTop(n), n = null) : (r.$timer = clearInterval(r.$timer), r.$scrollAnimation = null, t && t()) }, 10) }, this.scrollToY = function (e) { this.scrollTop !== e && (this.$loop.schedule(this.CHANGE_SCROLL), this.scrollTop = e) }, this.scrollToX = function (e) { this.scrollLeft !== e && (this.scrollLeft = e), this.$loop.schedule(this.CHANGE_H_SCROLL) }, this.scrollTo = function (e, t) { this.session.setScrollTop(t), this.session.setScrollLeft(t) }, this.scrollBy = function (e, t) { t && this.session.setScrollTop(this.session.getScrollTop() + t), e && this.session.setScrollLeft(this.session.getScrollLeft() + e) }, this.isScrollableBy = function (e, t) { if (t < 0 && this.session.getScrollTop() >= 1 - this.scrollMargin.top) return !0; if (t > 0 && this.session.getScrollTop() + this.$size.scrollerHeight - this.layerConfig.maxHeight < -1 + this.scrollMargin.bottom) return !0; if (e < 0 && this.session.getScrollLeft() >= 1 - this.scrollMargin.left) return !0; if (e > 0 && this.session.getScrollLeft() + this.$size.scrollerWidth - this.layerConfig.width < -1 + this.scrollMargin.right) return !0 }, this.pixelToScreenCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = Math.floor((t + this.scrollTop - n.top) / this.lineHeight), u = this.$blockCursor ? Math.floor(s) : Math.round(s); return { row: o, column: u, side: s - u > 0 ? 1 : -1, offsetX: i } }, this.screenToTextCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = this.$blockCursor ? Math.floor(s) : Math.round(s), u = Math.floor((t + this.scrollTop - n.top) / this.lineHeight); return this.session.screenToDocumentPosition(u, Math.max(o, 0), i) }, this.textToScreenCoordinates = function (e, t) { var n = this.scroller.getBoundingClientRect(), r = this.session.documentToScreenPosition(e, t), i = this.$padding + (this.session.$bidiHandler.isBidiRow(r.row, e) ? this.session.$bidiHandler.getPosLeft(r.column) : Math.round(r.column * this.characterWidth)), s = r.row * this.lineHeight; return { pageX: n.left + i - this.scrollLeft, pageY: n.top + s - this.scrollTop } }, this.visualizeFocus = function () { i.addCssClass(this.container, "ace_focus") }, this.visualizeBlur = function () { i.removeCssClass(this.container, "ace_focus") }, this.showComposition = function (e) { this.$composition = e, e.cssText || (e.cssText = this.textarea.style.cssText), e.useTextareaForIME == undefined && (e.useTextareaForIME = this.$useTextareaForIME), this.$useTextareaForIME ? (i.addCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = "", this.$moveTextAreaToCursor(), this.$cursorLayer.element.style.display = "none") : e.markerId = this.session.addMarker(e.markerRange, "ace_composition_marker", "text") }, this.setCompositionText = function (e) { var t = this.session.selection.cursor; this.addToken(e, "composition_placeholder", t.row, t.column), this.$moveTextAreaToCursor() }, this.hideComposition = function () { if (!this.$composition) return; this.$composition.markerId && this.session.removeMarker(this.$composition.markerId), i.removeCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = this.$composition.cssText; var e = this.session.selection.cursor; this.removeExtraToken(e.row, e.column), this.$composition = null, this.$cursorLayer.element.style.display = "" }, this.addToken = function (e, t, n, r) { var i = this.session; i.bgTokenizer.lines[n] = null; var s = { type: t, value: e }, o = i.getTokens(n); if (r == null) o.push(s); else { var u = 0; for (var a = 0; a < o.length; a++) { var f = o[a]; u += f.value.length; if (r <= u) { var l = f.value.length - (u - r), c = f.value.slice(0, l), h = f.value.slice(l); o.splice(a, 1, { type: f.type, value: c }, s, { type: f.type, value: h }); break } } } this.updateLines(n, n) }, this.removeExtraToken = function (e, t) { this.updateLines(e, e) }, this.setTheme = function (e, t) { function o(r) { if (n.$themeId != e) return t && t(); if (!r || !r.cssClass) throw new Error("couldn't load module " + e + " or it didn't call define"); r.$id && (n.$themeId = r.$id), i.importCssString(r.cssText, r.cssClass, n.container), n.theme && i.removeCssClass(n.container, n.theme.cssClass); var s = "padding" in r ? r.padding : "padding" in (n.theme || {}) ? 4 : n.$padding; n.$padding && s != n.$padding && n.setPadding(s), n.$theme = r.cssClass, n.theme = r, i.addCssClass(n.container, r.cssClass), i.setCssClass(n.container, "ace_dark", r.isDark), n.$size && (n.$size.width = 0, n.$updateSizeAsync()), n._dispatchEvent("themeLoaded", { theme: r }), t && t() } var n = this; this.$themeId = e, n._dispatchEvent("themeChange", { theme: e }); if (!e || typeof e == "string") { var r = e || this.$options.theme.initialValue; s.loadModule(["theme", r], o) } else o(e) }, this.getTheme = function () { return this.$themeId }, this.setStyle = function (e, t) { i.setCssClass(this.container, e, t !== !1) }, this.unsetStyle = function (e) { i.removeCssClass(this.container, e) }, this.setCursorStyle = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.setMouseCursor = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.attachToShadowRoot = function () { i.importCssString(v, "ace_editor.css", this.container) }, this.destroy = function () { this.freeze(), this.$fontMetrics.destroy(), this.$cursorLayer.destroy(), this.removeAllListeners(), this.container.textContent = "" } }).call(y.prototype), s.defineOptions(y.prototype, "renderer", { animatedScroll: { initialValue: !1 }, showInvisibles: { set: function (e) { this.$textLayer.setShowInvisibles(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !1 }, showPrintMargin: { set: function () { this.$updatePrintMargin() }, initialValue: !0 }, printMarginColumn: { set: function () { this.$updatePrintMargin() }, initialValue: 80 }, printMargin: { set: function (e) { typeof e == "number" && (this.$printMarginColumn = e), this.$showPrintMargin = !!e, this.$updatePrintMargin() }, get: function () { return this.$showPrintMargin && this.$printMarginColumn } }, showGutter: { set: function (e) { this.$gutter.style.display = e ? "block" : "none", this.$loop.schedule(this.CHANGE_FULL), this.onGutterResize() }, initialValue: !0 }, fadeFoldWidgets: { set: function (e) { i.setCssClass(this.$gutter, "ace_fade-fold-widgets", e) }, initialValue: !1 }, showFoldWidgets: { set: function (e) { this.$gutterLayer.setShowFoldWidgets(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, displayIndentGuides: { set: function (e) { this.$textLayer.setDisplayIndentGuides(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !0 }, highlightGutterLine: { set: function (e) { this.$gutterLayer.setHighlightGutterLine(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, hScrollBarAlwaysVisible: { set: function (e) { (!this.$hScrollBarAlwaysVisible || !this.$horizScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, vScrollBarAlwaysVisible: { set: function (e) { (!this.$vScrollBarAlwaysVisible || !this.$vScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, fontSize: { set: function (e) { typeof e == "number" && (e += "px"), this.container.style.fontSize = e, this.updateFontSize() }, initialValue: 12 }, fontFamily: { set: function (e) { this.container.style.fontFamily = e, this.updateFontSize() } }, maxLines: { set: function (e) { this.updateFull() } }, minLines: { set: function (e) { this.$minLines < 562949953421311 || (this.$minLines = 0), this.updateFull() } }, maxPixelHeight: { set: function (e) { this.updateFull() }, initialValue: 0 }, scrollPastEnd: { set: function (e) { e = +e || 0; if (this.$scrollPastEnd == e) return; this.$scrollPastEnd = e, this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: 0, handlesSet: !0 }, fixedWidthGutter: { set: function (e) { this.$gutterLayer.$fixedWidth = !!e, this.$loop.schedule(this.CHANGE_GUTTER) } }, theme: { set: function (e) { this.setTheme(e) }, get: function () { return this.$themeId || this.theme }, initialValue: "./theme/textmate", handlesSet: !0 }, hasCssTransforms: {}, useTextareaForIME: { initialValue: !m.isMobile && !m.isIE } }), t.VirtualRenderer = y }), define("ace/worker/worker_client", ["require", "exports", "module", "ace/lib/oop", "ace/lib/net", "ace/lib/event_emitter", "ace/config"], function (e, t, n) { "use strict"; function u(e) { var t = "importScripts('" + i.qualifyURL(e) + "');"; try { return new Blob([t], { type: "application/javascript" }) } catch (n) { var r = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder, s = new r; return s.append(t), s.getBlob("application/javascript") } } function a(e) { if (typeof Worker == "undefined") return { postMessage: function () { }, terminate: function () { } }; if (o.get("loadWorkerFromBlob")) { var t = u(e), n = window.URL || window.webkitURL, r = n.createObjectURL(t); return new Worker(r) } return new Worker(e) } var r = e("../lib/oop"), i = e("../lib/net"), s = e("../lib/event_emitter").EventEmitter, o = e("../config"), f = function (e) { e.postMessage || (e = this.$createWorkerFromOldConfig.apply(this, arguments)), this.$worker = e, this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this), this.changeListener = this.changeListener.bind(this), this.onMessage = this.onMessage.bind(this), this.callbackId = 1, this.callbacks = {}, this.$worker.onmessage = this.onMessage }; (function () { r.implement(this, s), this.$createWorkerFromOldConfig = function (t, n, r, i, s) { e.nameToUrl && !e.toUrl && (e.toUrl = e.nameToUrl); if (o.get("packaged") || !e.toUrl) i = i || o.moduleUrl(n, "worker"); else { var u = this.$normalizePath; i = i || u(e.toUrl("ace/worker/worker.js", null, "_")); var f = {}; t.forEach(function (t) { f[t] = u(e.toUrl(t, null, "_").replace(/(\.js)?(\?.*)?$/, "")) }) } return this.$worker = a(i), s && this.send("importScripts", s), this.$worker.postMessage({ init: !0, tlns: f, module: n, classname: r }), this.$worker }, this.onMessage = function (e) { var t = e.data; switch (t.type) { case "event": this._signal(t.name, { data: t.data }); break; case "call": var n = this.callbacks[t.id]; n && (n(t.data), delete this.callbacks[t.id]); break; case "error": this.reportError(t.data); break; case "log": window.console && console.log && console.log.apply(console, t.data) } }, this.reportError = function (e) { window.console && console.error && console.error(e) }, this.$normalizePath = function (e) { return i.qualifyURL(e) }, this.terminate = function () { this._signal("terminate", {}), this.deltaQueue = null, this.$worker.terminate(), this.$worker = null, this.$doc && this.$doc.off("change", this.changeListener), this.$doc = null }, this.send = function (e, t) { this.$worker.postMessage({ command: e, args: t }) }, this.call = function (e, t, n) { if (n) { var r = this.callbackId++; this.callbacks[r] = n, t.push(r) } this.send(e, t) }, this.emit = function (e, t) { try { t.data && t.data.err && (t.data.err = { message: t.data.err.message, stack: t.data.err.stack, code: t.data.err.code }), this.$worker.postMessage({ event: e, data: { data: t.data } }) } catch (n) { console.error(n.stack) } }, this.attachToDocument = function (e) { this.$doc && this.terminate(), this.$doc = e, this.call("setValue", [e.getValue()]), e.on("change", this.changeListener) }, this.changeListener = function (e) { this.deltaQueue || (this.deltaQueue = [], setTimeout(this.$sendDeltaQueue, 0)), e.action == "insert" ? this.deltaQueue.push(e.start, e.lines) : this.deltaQueue.push(e.start, e.end) }, this.$sendDeltaQueue = function () { var e = this.deltaQueue; if (!e) return; this.deltaQueue = null, e.length > 50 && e.length > this.$doc.getLength() >> 1 ? this.call("setValue", [this.$doc.getValue()]) : this.emit("change", { data: e }) } }).call(f.prototype); var l = function (e, t, n) { var r = null, i = !1, u = Object.create(s), a = [], l = new f({ messageBuffer: a, terminate: function () { }, postMessage: function (e) { a.push(e); if (!r) return; i ? setTimeout(c) : c() } }); l.setEmitSync = function (e) { i = e }; var c = function () { var e = a.shift(); e.command ? r[e.command].apply(r, e.args) : e.event && u._signal(e.event, e.data) }; return u.postMessage = function (e) { l.onMessage({ data: e }) }, u.callback = function (e, t) { this.postMessage({ type: "call", id: t, data: e }) }, u.emit = function (e, t) { this.postMessage({ type: "event", name: e, data: t }) }, o.loadModule(["worker", t], function (e) { r = new e[n](u); while (a.length) c() }), l }; t.UIWorkerClient = l, t.WorkerClient = f, t.createWorker = a }), define("ace/placeholder", ["require", "exports", "module", "ace/range", "ace/lib/event_emitter", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = e("./lib/event_emitter").EventEmitter, s = e("./lib/oop"), o = function (e, t, n, r, i, s) { var o = this; this.length = t, this.session = e, this.doc = e.getDocument(), this.mainClass = i, this.othersClass = s, this.$onUpdate = this.onUpdate.bind(this), this.doc.on("change", this.$onUpdate), this.$others = r, this.$onCursorChange = function () { setTimeout(function () { o.onCursorChange() }) }, this.$pos = n; var u = e.getUndoManager().$undoStack || e.getUndoManager().$undostack || { length: -1 }; this.$undoStackDepth = u.length, this.setup(), e.selection.on("changeCursor", this.$onCursorChange) }; (function () { s.implement(this, i), this.setup = function () { var e = this, t = this.doc, n = this.session; this.selectionBefore = n.selection.toJSON(), n.selection.inMultiSelectMode && n.selection.toSingleRange(), this.pos = t.createAnchor(this.$pos.row, this.$pos.column); var i = this.pos; i.$insertRight = !0, i.detach(), i.markerId = n.addMarker(new r(i.row, i.column, i.row, i.column + this.length), this.mainClass, null, !1), this.others = [], this.$others.forEach(function (n) { var r = t.createAnchor(n.row, n.column); r.$insertRight = !0, r.detach(), e.others.push(r) }), n.setUndoSelect(!1) }, this.showOtherMarkers = function () { if (this.othersActive) return; var e = this.session, t = this; this.othersActive = !0, this.others.forEach(function (n) { n.markerId = e.addMarker(new r(n.row, n.column, n.row, n.column + t.length), t.othersClass, null, !1) }) }, this.hideOtherMarkers = function () { if (!this.othersActive) return; this.othersActive = !1; for (var e = 0; e < this.others.length; e++)this.session.removeMarker(this.others[e].markerId) }, this.onUpdate = function (e) { if (this.$updating) return this.updateAnchors(e); var t = e; if (t.start.row !== t.end.row) return; if (t.start.row !== this.pos.row) return; this.$updating = !0; var n = e.action === "insert" ? t.end.column - t.start.column : t.start.column - t.end.column, i = t.start.column >= this.pos.column && t.start.column <= this.pos.column + this.length + 1, s = t.start.column - this.pos.column; this.updateAnchors(e), i && (this.length += n); if (i && !this.session.$fromUndo) if (e.action === "insert") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.insertMergedLines(a, e.lines) } else if (e.action === "remove") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.remove(new r(a.row, a.column, a.row, a.column - n)) } this.$updating = !1, this.updateMarkers() }, this.updateAnchors = function (e) { this.pos.onChange(e); for (var t = this.others.length; t--;)this.others[t].onChange(e); this.updateMarkers() }, this.updateMarkers = function () { if (this.$updating) return; var e = this, t = this.session, n = function (n, i) { t.removeMarker(n.markerId), n.markerId = t.addMarker(new r(n.row, n.column, n.row, n.column + e.length), i, null, !1) }; n(this.pos, this.mainClass); for (var i = this.others.length; i--;)n(this.others[i], this.othersClass) }, this.onCursorChange = function (e) { if (this.$updating || !this.session) return; var t = this.session.selection.getCursor(); t.row === this.pos.row && t.column >= this.pos.column && t.column <= this.pos.column + this.length ? (this.showOtherMarkers(), this._emit("cursorEnter", e)) : (this.hideOtherMarkers(), this._emit("cursorLeave", e)) }, this.detach = function () { this.session.removeMarker(this.pos && this.pos.markerId), this.hideOtherMarkers(), this.doc.off("change", this.$onUpdate), this.session.selection.off("changeCursor", this.$onCursorChange), this.session.setUndoSelect(!0), this.session = null }, this.cancel = function () { if (this.$undoStackDepth === -1) return; var e = this.session.getUndoManager(), t = (e.$undoStack || e.$undostack).length - this.$undoStackDepth; for (var n = 0; n < t; n++)e.undo(this.session, !0); this.selectionBefore && this.session.selection.fromJSON(this.selectionBefore) } }).call(o.prototype), t.PlaceHolder = o }), define("ace/mouse/multi_select_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { function s(e, t) { return e.row == t.row && e.column == t.column } function o(e) { var t = e.domEvent, n = t.altKey, o = t.shiftKey, u = t.ctrlKey, a = e.getAccelKey(), f = e.getButton(); u && i.isMac && (f = t.button); if (e.editor.inMultiSelectMode && f == 2) { e.editor.textInput.onContextMenu(e.domEvent); return } if (!u && !n && !a) { f === 0 && e.editor.inMultiSelectMode && e.editor.exitMultiSelectMode(); return } if (f !== 0) return; var l = e.editor, c = l.selection, h = l.inMultiSelectMode, p = e.getDocumentPosition(), d = c.getCursor(), v = e.inSelection() || c.isEmpty() && s(p, d), m = e.x, g = e.y, y = function (e) { m = e.clientX, g = e.clientY }, b = l.session, w = l.renderer.pixelToScreenCoordinates(m, g), E = w, S; if (l.$mouseHandler.$enableJumpToDef) u && n || a && n ? S = o ? "block" : "add" : n && l.$blockSelectEnabled && (S = "block"); else if (a && !n) { S = "add"; if (!h && o) return } else n && l.$blockSelectEnabled && (S = "block"); S && i.isMac && t.ctrlKey && l.$mouseHandler.cancelContextMenu(); if (S == "add") { if (!h && v) return; if (!h) { var x = c.toOrientedRange(); l.addSelectionMarker(x) } var T = c.rangeList.rangeAtPoint(p); l.inVirtualSelectionMode = !0, o && (T = null, x = c.ranges[0] || x, l.removeSelectionMarker(x)), l.once("mouseup", function () { var e = c.toOrientedRange(); T && e.isEmpty() && s(T.cursor, e.cursor) ? c.substractPoint(e.cursor) : (o ? c.substractPoint(x.cursor) : x && (l.removeSelectionMarker(x), c.addRange(x)), c.addRange(e)), l.inVirtualSelectionMode = !1 }) } else if (S == "block") { e.stop(), l.inVirtualSelectionMode = !0; var N, C = [], k = function () { var e = l.renderer.pixelToScreenCoordinates(m, g), t = b.screenToDocumentPosition(e.row, e.column, e.offsetX); if (s(E, e) && s(t, c.lead)) return; E = e, l.selection.moveToPosition(t), l.renderer.scrollCursorIntoView(), l.removeSelectionMarkers(C), C = c.rectangularRangeBlock(E, w), l.$mouseHandler.$clickSelection && C.length == 1 && C[0].isEmpty() && (C[0] = l.$mouseHandler.$clickSelection.clone()), C.forEach(l.addSelectionMarker, l), l.updateSelectionMarkers() }; h && !a ? c.toSingleRange() : !h && a && (N = c.toOrientedRange(), l.addSelectionMarker(N)), o ? w = b.documentToScreenPosition(c.lead) : c.moveToPosition(p), E = { row: -1, column: -1 }; var L = function (e) { k(), clearInterval(O), l.removeSelectionMarkers(C), C.length || (C = [c.toOrientedRange()]), N && (l.removeSelectionMarker(N), c.toSingleRange(N)); for (var t = 0; t < C.length; t++)c.addRange(C[t]); l.inVirtualSelectionMode = !1, l.$mouseHandler.$clickSelection = null }, A = k; r.capture(l.container, y, L); var O = setInterval(function () { A() }, 20); return e.preventDefault() } } var r = e("../lib/event"), i = e("../lib/useragent"); t.onMouseDown = o }), define("ace/commands/multi_select_commands", ["require", "exports", "module", "ace/keyboard/hash_handler"], function (e, t, n) { t.defaultCommands = [{ name: "addCursorAbove", description: "Add cursor above", exec: function (e) { e.selectMoreLines(-1) }, bindKey: { win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelow", description: "Add cursor below", exec: function (e) { e.selectMoreLines(1) }, bindKey: { win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorAboveSkipCurrent", description: "Add cursor above (skip current)", exec: function (e) { e.selectMoreLines(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelowSkipCurrent", description: "Add cursor below (skip current)", exec: function (e) { e.selectMoreLines(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreBefore", description: "Select more before", exec: function (e) { e.selectMore(-1) }, bindKey: { win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreAfter", description: "Select more after", exec: function (e) { e.selectMore(1) }, bindKey: { win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextBefore", description: "Select next before", exec: function (e) { e.selectMore(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextAfter", description: "Select next after", exec: function (e) { e.selectMore(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "toggleSplitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.rangeCount > 1 ? e.multiSelect.joinSelections() : e.multiSelect.splitIntoLines() }, bindKey: { win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L" }, readOnly: !0 }, { name: "splitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.splitIntoLines() }, readOnly: !0 }, { name: "alignCursors", description: "Align cursors", exec: function (e) { e.alignCursors() }, bindKey: { win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A" }, scrollIntoView: "cursor" }, { name: "findAll", description: "Find all", exec: function (e) { e.findAll() }, bindKey: { win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G" }, scrollIntoView: "cursor", readOnly: !0 }], t.multiSelectCommands = [{ name: "singleSelection", description: "Single selection", bindKey: "esc", exec: function (e) { e.exitMultiSelectMode() }, scrollIntoView: "cursor", readOnly: !0, isAvailable: function (e) { return e && e.inMultiSelectMode } }]; var r = e("../keyboard/hash_handler").HashHandler; t.keyboardHandler = new r(t.multiSelectCommands) }), define("ace/multi_select", ["require", "exports", "module", "ace/range_list", "ace/range", "ace/selection", "ace/mouse/multi_select_handler", "ace/lib/event", "ace/lib/lang", "ace/commands/multi_select_commands", "ace/search", "ace/edit_session", "ace/editor", "ace/config"], function (e, t, n) { function h(e, t, n) { return c.$options.wrap = !0, c.$options.needle = t, c.$options.backwards = n == -1, c.find(e) } function v(e, t) { return e.row == t.row && e.column == t.column } function m(e) { if (e.$multiselectOnSessionChange) return; e.$onAddRange = e.$onAddRange.bind(e), e.$onRemoveRange = e.$onRemoveRange.bind(e), e.$onMultiSelect = e.$onMultiSelect.bind(e), e.$onSingleSelect = e.$onSingleSelect.bind(e), e.$multiselectOnSessionChange = t.onSessionChange.bind(e), e.$checkMultiselectChange = e.$checkMultiselectChange.bind(e), e.$multiselectOnSessionChange(e), e.on("changeSession", e.$multiselectOnSessionChange), e.on("mousedown", o), e.commands.addCommands(f.defaultCommands), g(e) } function g(e) { function r(t) { n && (e.renderer.setMouseCursor(""), n = !1) } if (!e.textInput) return; var t = e.textInput.getElement(), n = !1; u.addListener(t, "keydown", function (t) { var i = t.keyCode == 18 && !(t.ctrlKey || t.shiftKey || t.metaKey); e.$blockSelectEnabled && i ? n || (e.renderer.setMouseCursor("crosshair"), n = !0) : n && r() }, e), u.addListener(t, "keyup", r, e), u.addListener(t, "blur", r, e) } var r = e("./range_list").RangeList, i = e("./range").Range, s = e("./selection").Selection, o = e("./mouse/multi_select_handler").onMouseDown, u = e("./lib/event"), a = e("./lib/lang"), f = e("./commands/multi_select_commands"); t.commands = f.defaultCommands.concat(f.multiSelectCommands); var l = e("./search").Search, c = new l, p = e("./edit_session").EditSession; (function () { this.getSelectionMarkers = function () { return this.$selectionMarkers } }).call(p.prototype), function () { this.ranges = null, this.rangeList = null, this.addRange = function (e, t) { if (!e) return; if (!this.inMultiSelectMode && this.rangeCount === 0) { var n = this.toOrientedRange(); this.rangeList.add(n), this.rangeList.add(e); if (this.rangeList.ranges.length != 2) return this.rangeList.removeAll(), t || this.fromOrientedRange(e); this.rangeList.removeAll(), this.rangeList.add(n), this.$onAddRange(n) } e.cursor || (e.cursor = e.end); var r = this.rangeList.add(e); return this.$onAddRange(e), r.length && this.$onRemoveRange(r), this.rangeCount > 1 && !this.inMultiSelectMode && (this._signal("multiSelect"), this.inMultiSelectMode = !0, this.session.$undoSelect = !1, this.rangeList.attach(this.session)), t || this.fromOrientedRange(e) }, this.toSingleRange = function (e) { e = e || this.ranges[0]; var t = this.rangeList.removeAll(); t.length && this.$onRemoveRange(t), e && this.fromOrientedRange(e) }, this.substractPoint = function (e) { var t = this.rangeList.substractPoint(e); if (t) return this.$onRemoveRange(t), t[0] }, this.mergeOverlappingRanges = function () { var e = this.rangeList.merge(); e.length && this.$onRemoveRange(e) }, this.$onAddRange = function (e) { this.rangeCount = this.rangeList.ranges.length, this.ranges.unshift(e), this._signal("addRange", { range: e }) }, this.$onRemoveRange = function (e) { this.rangeCount = this.rangeList.ranges.length; if (this.rangeCount == 1 && this.inMultiSelectMode) { var t = this.rangeList.ranges.pop(); e.push(t), this.rangeCount = 0 } for (var n = e.length; n--;) { var r = this.ranges.indexOf(e[n]); this.ranges.splice(r, 1) } this._signal("removeRange", { ranges: e }), this.rangeCount === 0 && this.inMultiSelectMode && (this.inMultiSelectMode = !1, this._signal("singleSelect"), this.session.$undoSelect = !0, this.rangeList.detach(this.session)), t = t || this.ranges[0], t && !t.isEqual(this.getRange()) && this.fromOrientedRange(t) }, this.$initRangeList = function () { if (this.rangeList) return; this.rangeList = new r, this.ranges = [], this.rangeCount = 0 }, this.getAllRanges = function () { return this.rangeCount ? this.rangeList.ranges.concat() : [this.getRange()] }, this.splitIntoLines = function () { var e = this.ranges.length ? this.ranges : [this.getRange()], t = []; for (var n = 0; n < e.length; n++) { var r = e[n], s = r.start.row, o = r.end.row; if (s === o) t.push(r.clone()); else { t.push(new i(s, r.start.column, s, this.session.getLine(s).length)); while (++s < o) t.push(this.getLineRange(s, !0)); t.push(new i(o, 0, o, r.end.column)) } n == 0 && !this.isBackwards() && (t = t.reverse()) } this.toSingleRange(); for (var n = t.length; n--;)this.addRange(t[n]) }, this.joinSelections = function () { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) }, this.toggleBlockSelection = function () { if (this.rangeCount > 1) { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) } else { var r = this.session.documentToScreenPosition(this.cursor), s = this.session.documentToScreenPosition(this.anchor), o = this.rectangularRangeBlock(r, s); o.forEach(this.addRange, this) } }, this.rectangularRangeBlock = function (e, t, n) { var r = [], s = e.column < t.column; if (s) var o = e.column, u = t.column, a = e.offsetX, f = t.offsetX; else var o = t.column, u = e.column, a = t.offsetX, f = e.offsetX; var l = e.row < t.row; if (l) var c = e.row, h = t.row; else var c = t.row, h = e.row; o < 0 && (o = 0), c < 0 && (c = 0), c == h && (n = !0); var p; for (var d = c; d <= h; d++) { var m = i.fromPoints(this.session.screenToDocumentPosition(d, o, a), this.session.screenToDocumentPosition(d, u, f)); if (m.isEmpty()) { if (p && v(m.end, p)) break; p = m.end } m.cursor = s ? m.start : m.end, r.push(m) } l && r.reverse(); if (!n) { var g = r.length - 1; while (r[g].isEmpty() && g > 0) g--; if (g > 0) { var y = 0; while (r[y].isEmpty()) y++ } for (var b = g; b >= y; b--)r[b].isEmpty() && r.splice(b, 1) } return r } }.call(s.prototype); var d = e("./editor").Editor; (function () { this.updateSelectionMarkers = function () { this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.addSelectionMarker = function (e) { e.cursor || (e.cursor = e.end); var t = this.getSelectionStyle(); return e.marker = this.session.addMarker(e, "ace_selection", t), this.session.$selectionMarkers.push(e), this.session.selectionMarkerCount = this.session.$selectionMarkers.length, e }, this.removeSelectionMarker = function (e) { if (!e.marker) return; this.session.removeMarker(e.marker); var t = this.session.$selectionMarkers.indexOf(e); t != -1 && this.session.$selectionMarkers.splice(t, 1), this.session.selectionMarkerCount = this.session.$selectionMarkers.length }, this.removeSelectionMarkers = function (e) { var t = this.session.$selectionMarkers; for (var n = e.length; n--;) { var r = e[n]; if (!r.marker) continue; this.session.removeMarker(r.marker); var i = t.indexOf(r); i != -1 && t.splice(i, 1) } this.session.selectionMarkerCount = t.length }, this.$onAddRange = function (e) { this.addSelectionMarker(e.range), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onRemoveRange = function (e) { this.removeSelectionMarkers(e.ranges), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onMultiSelect = function (e) { if (this.inMultiSelectMode) return; this.inMultiSelectMode = !0, this.setStyle("ace_multiselect"), this.keyBinding.addKeyboardHandler(f.keyboardHandler), this.commands.setDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onSingleSelect = function (e) { if (this.session.multiSelect.inVirtualMode) return; this.inMultiSelectMode = !1, this.unsetStyle("ace_multiselect"), this.keyBinding.removeKeyboardHandler(f.keyboardHandler), this.commands.removeDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers(), this._emit("changeSelection") }, this.$onMultiSelectExec = function (e) { var t = e.command, n = e.editor; if (!n.multiSelect) return; if (!t.multiSelectAction) { var r = t.exec(n, e.args || {}); n.multiSelect.addRange(n.multiSelect.toOrientedRange()), n.multiSelect.mergeOverlappingRanges() } else t.multiSelectAction == "forEach" ? r = n.forEachSelection(t, e.args) : t.multiSelectAction == "forEachLine" ? r = n.forEachSelection(t, e.args, !0) : t.multiSelectAction == "single" ? (n.exitMultiSelectMode(), r = t.exec(n, e.args || {})) : r = t.multiSelectAction(n, e.args || {}); return r }, this.forEachSelection = function (e, t, n) { if (this.inVirtualSelectionMode) return; var r = n && n.keepOrder, i = n == 1 || n && n.$byLines, o = this.session, u = this.selection, a = u.rangeList, f = (r ? u : a).ranges, l; if (!f.length) return e.exec ? e.exec(this, t || {}) : e(this, t || {}); var c = u._eventRegistry; u._eventRegistry = {}; var h = new s(o); this.inVirtualSelectionMode = !0; for (var p = f.length; p--;) { if (i) while (p > 0 && f[p].start.row == f[p - 1].end.row) p--; h.fromOrientedRange(f[p]), h.index = p, this.selection = o.selection = h; var d = e.exec ? e.exec(this, t || {}) : e(this, t || {}); !l && d !== undefined && (l = d), h.toOrientedRange(f[p]) } h.detach(), this.selection = o.selection = u, this.inVirtualSelectionMode = !1, u._eventRegistry = c, u.mergeOverlappingRanges(), u.ranges[0] && u.fromOrientedRange(u.ranges[0]); var v = this.renderer.$scrollAnimation; return this.onCursorChange(), this.onSelectionChange(), v && v.from == v.to && this.renderer.animateScrolling(v.from), l }, this.exitMultiSelectMode = function () { if (!this.inMultiSelectMode || this.inVirtualSelectionMode) return; this.multiSelect.toSingleRange() }, this.getSelectedText = function () { var e = ""; if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var t = this.multiSelect.rangeList.ranges, n = []; for (var r = 0; r < t.length; r++)n.push(this.session.getTextRange(t[r])); var i = this.session.getDocument().getNewLineCharacter(); e = n.join(i), e.length == (n.length - 1) * i.length && (e = "") } else this.selection.isEmpty() || (e = this.session.getTextRange(this.getSelectionRange())); return e }, this.$checkMultiselectChange = function (e, t) { if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var n = this.multiSelect.ranges[0]; if (this.multiSelect.isEmpty() && t == this.multiSelect.anchor) return; var r = t == this.multiSelect.anchor ? n.cursor == n.start ? n.end : n.start : n.cursor; r.row != t.row || this.session.$clipPositionToDocument(r.row, r.column).column != t.column ? this.multiSelect.toSingleRange(this.multiSelect.toOrientedRange()) : this.multiSelect.mergeOverlappingRanges() } }, this.findAll = function (e, t, n) { t = t || {}, t.needle = e || t.needle; if (t.needle == undefined) { var r = this.selection.isEmpty() ? this.selection.getWordRange() : this.selection.getRange(); t.needle = this.session.getTextRange(r) } this.$search.set(t); var i = this.$search.findAll(this.session); if (!i.length) return 0; var s = this.multiSelect; n || s.toSingleRange(i[0]); for (var o = i.length; o--;)s.addRange(i[o], !0); return r && s.rangeList.rangeAtPoint(r.start) && s.addRange(r, !0), i.length }, this.selectMoreLines = function (e, t) { var n = this.selection.toOrientedRange(), r = n.cursor == n.end, s = this.session.documentToScreenPosition(n.cursor); this.selection.$desiredColumn && (s.column = this.selection.$desiredColumn); var o = this.session.screenToDocumentPosition(s.row + e, s.column); if (!n.isEmpty()) var u = this.session.documentToScreenPosition(r ? n.end : n.start), a = this.session.screenToDocumentPosition(u.row + e, u.column); else var a = o; if (r) { var f = i.fromPoints(o, a); f.cursor = f.start } else { var f = i.fromPoints(a, o); f.cursor = f.end } f.desiredColumn = s.column; if (!this.selection.inMultiSelectMode) this.selection.addRange(n); else if (t) var l = n.cursor; this.selection.addRange(f), l && this.selection.substractPoint(l) }, this.transposeSelections = function (e) { var t = this.session, n = t.multiSelect, r = n.ranges; for (var i = r.length; i--;) { var s = r[i]; if (s.isEmpty()) { var o = t.getWordRange(s.start.row, s.start.column); s.start.row = o.start.row, s.start.column = o.start.column, s.end.row = o.end.row, s.end.column = o.end.column } } n.mergeOverlappingRanges(); var u = []; for (var i = r.length; i--;) { var s = r[i]; u.unshift(t.getTextRange(s)) } e < 0 ? u.unshift(u.pop()) : u.push(u.shift()); for (var i = r.length; i--;) { var s = r[i], o = s.clone(); t.replace(s, u[i]), s.start.row = o.start.row, s.start.column = o.start.column } n.fromOrientedRange(n.ranges[0]) }, this.selectMore = function (e, t, n) { var r = this.session, i = r.multiSelect, s = i.toOrientedRange(); if (s.isEmpty()) { s = r.getWordRange(s.start.row, s.start.column), s.cursor = e == -1 ? s.start : s.end, this.multiSelect.addRange(s); if (n) return } var o = r.getTextRange(s), u = h(r, o, e); u && (u.cursor = e == -1 ? u.start : u.end, this.session.unfold(u), this.multiSelect.addRange(u), this.renderer.scrollCursorIntoView(null, .5)), t && this.multiSelect.substractPoint(s.cursor) }, this.alignCursors = function () { var e = this.session, t = e.multiSelect, n = t.ranges, r = -1, s = n.filter(function (e) { if (e.cursor.row == r) return !0; r = e.cursor.row }); if (!n.length || s.length == n.length - 1) { var o = this.selection.getRange(), u = o.start.row, f = o.end.row, l = u == f; if (l) { var c = this.session.getLength(), h; do h = this.session.getLine(f); while (/[=:]/.test(h) && ++f < c); do h = this.session.getLine(u); while (/[=:]/.test(h) && --u > 0); u < 0 && (u = 0), f >= c && (f = c - 1) } var p = this.session.removeFullLines(u, f); p = this.$reAlignText(p, l), this.session.insert({ row: u, column: 0 }, p.join("\n") + "\n"), l || (o.start.column = 0, o.end.column = p[p.length - 1].length), this.selection.setRange(o) } else { s.forEach(function (e) { t.substractPoint(e.cursor) }); var d = 0, v = Infinity, m = n.map(function (t) { var n = t.cursor, r = e.getLine(n.row), i = r.substr(n.column).search(/\S/g); return i == -1 && (i = 0), n.column > d && (d = n.column), i < v && (v = i), i }); n.forEach(function (t, n) { var r = t.cursor, s = d - r.column, o = m[n] - v; s > o ? e.insert(r, a.stringRepeat(" ", s - o)) : e.remove(new i(r.row, r.column, r.row, r.column - s + o)), t.start.column = t.end.column = d, t.start.row = t.end.row = r.row, t.cursor = t.end }), t.fromOrientedRange(n[0]), this.renderer.updateCursor(), this.renderer.updateBackMarkers() } }, this.$reAlignText = function (e, t) { function u(e) { return a.stringRepeat(" ", e) } function f(e) { return e[2] ? u(i) + e[2] + u(s - e[2].length + o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function l(e) { return e[2] ? u(i + s - e[2].length) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function c(e) { return e[2] ? u(i) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } var n = !0, r = !0, i, s, o; return e.map(function (e) { var t = e.match(/(\s*)(.*?)(\s*)([=:].*)/); return t ? i == null ? (i = t[1].length, s = t[2].length, o = t[3].length, t) : (i + s + o != t[1].length + t[2].length + t[3].length && (r = !1), i != t[1].length && (n = !1), i > t[1].length && (i = t[1].length), s < t[2].length && (s = t[2].length), o > t[3].length && (o = t[3].length), t) : [e] }).map(t ? f : n ? r ? l : f : c) } }).call(d.prototype), t.onSessionChange = function (e) { var t = e.session; t && !t.multiSelect && (t.$selectionMarkers = [], t.selection.$initRangeList(), t.multiSelect = t.selection), this.multiSelect = t && t.multiSelect; var n = e.oldSession; n && (n.multiSelect.off("addRange", this.$onAddRange), n.multiSelect.off("removeRange", this.$onRemoveRange), n.multiSelect.off("multiSelect", this.$onMultiSelect), n.multiSelect.off("singleSelect", this.$onSingleSelect), n.multiSelect.lead.off("change", this.$checkMultiselectChange), n.multiSelect.anchor.off("change", this.$checkMultiselectChange)), t && (t.multiSelect.on("addRange", this.$onAddRange), t.multiSelect.on("removeRange", this.$onRemoveRange), t.multiSelect.on("multiSelect", this.$onMultiSelect), t.multiSelect.on("singleSelect", this.$onSingleSelect), t.multiSelect.lead.on("change", this.$checkMultiselectChange), t.multiSelect.anchor.on("change", this.$checkMultiselectChange)), t && this.inMultiSelectMode != t.selection.inMultiSelectMode && (t.selection.inMultiSelectMode ? this.$onMultiSelect() : this.$onSingleSelect()) }, t.MultiSelect = m, e("./config").defineOptions(d.prototype, "editor", { enableMultiselect: { set: function (e) { m(this), e ? (this.on("changeSession", this.$multiselectOnSessionChange), this.on("mousedown", o)) : (this.off("changeSession", this.$multiselectOnSessionChange), this.off("mousedown", o)) }, value: !0 }, enableBlockSelect: { set: function (e) { this.$blockSelectEnabled = e }, value: !0 } }) }), define("ace/mode/folding/fold_mode", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../range").Range, i = t.FoldMode = function () { }; (function () { this.foldingStartMarker = null, this.foldingStopMarker = null, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n); return this.foldingStartMarker.test(r) ? "start" : t == "markbeginend" && this.foldingStopMarker && this.foldingStopMarker.test(r) ? "end" : "" }, this.getFoldWidgetRange = function (e, t, n) { return null }, this.indentationBlock = function (e, t, n) { var i = /\S/, s = e.getLine(t), o = s.search(i); if (o == -1) return; var u = n || s.length, a = e.getLength(), f = t, l = t; while (++t < a) { var c = e.getLine(t).search(i); if (c == -1) continue; if (c <= o) { var h = e.getTokenAt(t, 0); if (!h || h.type !== "string") break } l = t } if (l > f) { var p = e.getLine(l).length; return new r(f, u, l, p) } }, this.openingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i + 1 }, u = e.$findClosingBracket(t, o, s); if (!u) return; var a = e.foldWidgets[u.row]; return a == null && (a = e.getFoldWidget(u.row)), a == "start" && u.row > o.row && (u.row--, u.column = e.getLine(u.row).length), r.fromPoints(o, u) }, this.closingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i }, u = e.$findOpeningBracket(t, o); if (!u) return; return u.column++, o.column--, r.fromPoints(u, o) } }).call(i.prototype) }), define("ace/theme/textmate", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; t.isDark = !1, t.cssClass = "ace-tm", t.cssText = '.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}', t.$id = "ace/theme/textmate"; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }), define("ace/line_widgets", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { this.session = e, this.session.widgetManager = this, this.session.getRowLength = this.getRowLength, this.session.$getWidgetScreenLength = this.$getWidgetScreenLength, this.updateOnChange = this.updateOnChange.bind(this), this.renderWidgets = this.renderWidgets.bind(this), this.measureWidgets = this.measureWidgets.bind(this), this.session._changedWidgets = [], this.$onChangeEditor = this.$onChangeEditor.bind(this), this.session.on("change", this.updateOnChange), this.session.on("changeFold", this.updateOnFold), this.session.on("changeEditor", this.$onChangeEditor) } var r = e("./lib/dom"); (function () { this.getRowLength = function (e) { var t; return this.lineWidgets ? t = this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0 : t = 0, !this.$useWrapMode || !this.$wrapData[e] ? 1 + t : this.$wrapData[e].length + 1 + t }, this.$getWidgetScreenLength = function () { var e = 0; return this.lineWidgets.forEach(function (t) { t && t.rowCount && !t.hidden && (e += t.rowCount) }), e }, this.$onChangeEditor = function (e) { this.attach(e.editor) }, this.attach = function (e) { e && e.widgetManager && e.widgetManager != this && e.widgetManager.detach(); if (this.editor == e) return; this.detach(), this.editor = e, e && (e.widgetManager = this, e.renderer.on("beforeRender", this.measureWidgets), e.renderer.on("afterRender", this.renderWidgets)) }, this.detach = function (e) { var t = this.editor; if (!t) return; this.editor = null, t.widgetManager = null, t.renderer.off("beforeRender", this.measureWidgets), t.renderer.off("afterRender", this.renderWidgets); var n = this.session.lineWidgets; n && n.forEach(function (e) { e && e.el && e.el.parentNode && (e._inDocument = !1, e.el.parentNode.removeChild(e.el)) }) }, this.updateOnFold = function (e, t) { var n = t.lineWidgets; if (!n || !e.action) return; var r = e.data, i = r.start.row, s = r.end.row, o = e.action == "add"; for (var u = i + 1; u < s; u++)n[u] && (n[u].hidden = o); n[s] && (o ? n[i] ? n[s].hidden = o : n[i] = n[s] : (n[i] == n[s] && (n[i] = undefined), n[s].hidden = o)) }, this.updateOnChange = function (e) { var t = this.session.lineWidgets; if (!t) return; var n = e.start.row, r = e.end.row - n; if (r !== 0) if (e.action == "remove") { var i = t.splice(n + 1, r); !t[n] && i[i.length - 1] && (t[n] = i.pop()), i.forEach(function (e) { e && this.removeLineWidget(e) }, this), this.$updateRows() } else { var s = new Array(r); t[n] && t[n].column != null && e.start.column > t[n].column && n++, s.unshift(n, 0), t.splice.apply(t, s), this.$updateRows() } }, this.$updateRows = function () { var e = this.session.lineWidgets; if (!e) return; var t = !0; e.forEach(function (e, n) { if (e) { t = !1, e.row = n; while (e.$oldWidget) e.$oldWidget.row = n, e = e.$oldWidget } }), t && (this.session.lineWidgets = null) }, this.$registerLineWidget = function (e) { this.session.lineWidgets || (this.session.lineWidgets = new Array(this.session.getLength())); var t = this.session.lineWidgets[e.row]; return t && (e.$oldWidget = t, t.el && t.el.parentNode && (t.el.parentNode.removeChild(t.el), t._inDocument = !1)), this.session.lineWidgets[e.row] = e, e }, this.addLineWidget = function (e) { this.$registerLineWidget(e), e.session = this.session; if (!this.editor) return e; var t = this.editor.renderer; e.html && !e.el && (e.el = r.createElement("div"), e.el.innerHTML = e.html), e.el && (r.addCssClass(e.el, "ace_lineWidgetContainer"), e.el.style.position = "absolute", e.el.style.zIndex = 5, t.container.appendChild(e.el), e._inDocument = !0, e.coverGutter || (e.el.style.zIndex = 3), e.pixelHeight == null && (e.pixelHeight = e.el.offsetHeight)), e.rowCount == null && (e.rowCount = e.pixelHeight / t.layerConfig.lineHeight); var n = this.session.getFoldAt(e.row, 0); e.$fold = n; if (n) { var i = this.session.lineWidgets; e.row == n.end.row && !i[n.start.row] ? i[n.start.row] = e : e.hidden = !0 } return this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows(), this.renderWidgets(null, t), this.onWidgetChanged(e), e }, this.removeLineWidget = function (e) { e._inDocument = !1, e.session = null, e.el && e.el.parentNode && e.el.parentNode.removeChild(e.el); if (e.editor && e.editor.destroy) try { e.editor.destroy() } catch (t) { } if (this.session.lineWidgets) { var n = this.session.lineWidgets[e.row]; if (n == e) this.session.lineWidgets[e.row] = e.$oldWidget, e.$oldWidget && this.onWidgetChanged(e.$oldWidget); else while (n) { if (n.$oldWidget == e) { n.$oldWidget = e.$oldWidget; break } n = n.$oldWidget } } this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows() }, this.getWidgetsAtRow = function (e) { var t = this.session.lineWidgets, n = t && t[e], r = []; while (n) r.push(n), n = n.$oldWidget; return r }, this.onWidgetChanged = function (e) { this.session._changedWidgets.push(e), this.editor && this.editor.renderer.updateFull() }, this.measureWidgets = function (e, t) { var n = this.session._changedWidgets, r = t.layerConfig; if (!n || !n.length) return; var i = Infinity; for (var s = 0; s < n.length; s++) { var o = n[s]; if (!o || !o.el) continue; if (o.session != this.session) continue; if (!o._inDocument) { if (this.session.lineWidgets[o.row] != o) continue; o._inDocument = !0, t.container.appendChild(o.el) } o.h = o.el.offsetHeight, o.fixedWidth || (o.w = o.el.offsetWidth, o.screenWidth = Math.ceil(o.w / r.characterWidth)); var u = o.h / r.lineHeight; o.coverLine && (u -= this.session.getRowLineCount(o.row), u < 0 && (u = 0)), o.rowCount != u && (o.rowCount = u, o.row < i && (i = o.row)) } i != Infinity && (this.session._emit("changeFold", { data: { start: { row: i } } }), this.session.lineWidgetWidth = null), this.session._changedWidgets = [] }, this.renderWidgets = function (e, t) { var n = t.layerConfig, r = this.session.lineWidgets; if (!r) return; var i = Math.min(this.firstRow, n.firstRow), s = Math.max(this.lastRow, n.lastRow, r.length); while (i > 0 && !r[i]) i--; this.firstRow = n.firstRow, this.lastRow = n.lastRow, t.$cursorLayer.config = n; for (var o = i; o <= s; o++) { var u = r[o]; if (!u || !u.el) continue; if (u.hidden) { u.el.style.top = -100 - (u.pixelHeight || 0) + "px"; continue } u._inDocument || (u._inDocument = !0, t.container.appendChild(u.el)); var a = t.$cursorLayer.getPixelPosition({ row: o, column: 0 }, !0).top; u.coverLine || (a += n.lineHeight * this.session.getRowLineCount(u.row)), u.el.style.top = a - n.offset + "px"; var f = u.coverGutter ? 0 : t.gutterWidth; u.fixedWidth || (f -= t.scrollLeft), u.el.style.left = f + "px", u.fullWidth && u.screenWidth && (u.el.style.minWidth = n.width + 2 * n.padding + "px"), u.fixedWidth ? u.el.style.right = t.scrollBar.getWidth() + "px" : u.el.style.right = "" } } }).call(i.prototype), t.LineWidgets = i }), define("ace/ext/error_marker", ["require", "exports", "module", "ace/line_widgets", "ace/lib/dom", "ace/range"], function (e, t, n) { "use strict"; function o(e, t, n) { var r = 0, i = e.length - 1; while (r <= i) { var s = r + i >> 1, o = n(t, e[s]); if (o > 0) r = s + 1; else { if (!(o < 0)) return s; i = s - 1 } } return -(r + 1) } function u(e, t, n) { var r = e.getAnnotations().sort(s.comparePoints); if (!r.length) return; var i = o(r, { row: t, column: -1 }, s.comparePoints); i < 0 && (i = -i - 1), i >= r.length ? i = n > 0 ? 0 : r.length - 1 : i === 0 && n < 0 && (i = r.length - 1); var u = r[i]; if (!u || !n) return; if (u.row === t) { do u = r[i += n]; while (u && u.row === t); if (!u) return r.slice() } var a = []; t = u.row; do a[n < 0 ? "unshift" : "push"](u), u = r[i += n]; while (u && u.row == t); return a.length && a } var r = e("../line_widgets").LineWidgets, i = e("../lib/dom"), s = e("../range").Range; t.showErrorMarker = function (e, t) { var n = e.session; n.widgetManager || (n.widgetManager = new r(n), n.widgetManager.attach(e)); var s = e.getCursorPosition(), o = s.row, a = n.widgetManager.getWidgetsAtRow(o).filter(function (e) { return e.type == "errorMarker" })[0]; a ? a.destroy() : o -= t; var f = u(n, o, t), l; if (f) { var c = f[0]; s.column = (c.pos && typeof c.column != "number" ? c.pos.sc : c.column) || 0, s.row = c.row, l = e.renderer.$gutterLayer.$annotations[s.row] } else { if (a) return; l = { text: ["Looks good!"], className: "ace_ok" } } e.session.unfold(s.row), e.selection.moveToPosition(s); var h = { row: s.row, fixedWidth: !0, coverGutter: !0, el: i.createElement("div"), type: "errorMarker" }, p = h.el.appendChild(i.createElement("div")), d = h.el.appendChild(i.createElement("div")); d.className = "error_widget_arrow " + l.className; var v = e.renderer.$cursorLayer.getPixelPosition(s).left; d.style.left = v + e.renderer.gutterWidth - 5 + "px", h.el.className = "error_widget_wrapper", p.className = "error_widget " + l.className, p.innerHTML = l.text.join("
"), p.appendChild(i.createElement("div")); var m = function (e, t, n) { if (t === 0 && (n === "esc" || n === "return")) return h.destroy(), { command: "null" } }; h.destroy = function () { if (e.$mouseHandler.isMousePressed) return; e.keyBinding.removeKeyboardHandler(m), n.widgetManager.removeLineWidget(h), e.off("changeSelection", h.destroy), e.off("changeSession", h.destroy), e.off("mouseup", h.destroy), e.off("change", h.destroy) }, e.keyBinding.addKeyboardHandler(m), e.on("changeSelection", h.destroy), e.on("changeSession", h.destroy), e.on("mouseup", h.destroy), e.on("change", h.destroy), e.session.widgetManager.addLineWidget(h), h.el.onmousedown = e.focus.bind(e), e.renderer.scrollCursorIntoView(null, .5, { bottom: h.el.offsetHeight }) }, i.importCssString(" .error_widget_wrapper { background: inherit; color: inherit; border:none } .error_widget { border-top: solid 2px; border-bottom: solid 2px; margin: 5px 0; padding: 10px 40px; white-space: pre-wrap; } .error_widget.ace_error, .error_widget_arrow.ace_error{ border-color: #ff5a5a } .error_widget.ace_warning, .error_widget_arrow.ace_warning{ border-color: #F1D817 } .error_widget.ace_info, .error_widget_arrow.ace_info{ border-color: #5a5a5a } .error_widget.ace_ok, .error_widget_arrow.ace_ok{ border-color: #5aaa5a } .error_widget_arrow { position: absolute; border: solid 5px; border-top-color: transparent!important; border-right-color: transparent!important; border-left-color: transparent!important; top: -5px; }", "") }), define("ace/ace", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/dom", "ace/lib/event", "ace/range", "ace/editor", "ace/edit_session", "ace/undomanager", "ace/virtual_renderer", "ace/worker/worker_client", "ace/keyboard/hash_handler", "ace/placeholder", "ace/multi_select", "ace/mode/folding/fold_mode", "ace/theme/textmate", "ace/ext/error_marker", "ace/config"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/dom"), i = e("./lib/event"), s = e("./range").Range, o = e("./editor").Editor, u = e("./edit_session").EditSession, a = e("./undomanager").UndoManager, f = e("./virtual_renderer").VirtualRenderer; e("./worker/worker_client"), e("./keyboard/hash_handler"), e("./placeholder"), e("./multi_select"), e("./mode/folding/fold_mode"), e("./theme/textmate"), e("./ext/error_marker"), t.config = e("./config"), t.require = e, typeof define == "function" && (t.define = define), t.edit = function (e, n) { if (typeof e == "string") { var s = e; e = document.getElementById(s); if (!e) throw new Error("ace.edit can't find div #" + s) } if (e && e.env && e.env.editor instanceof o) return e.env.editor; var u = ""; if (e && /input|textarea/i.test(e.tagName)) { var a = e; u = a.value, e = r.createElement("pre"), a.parentNode.replaceChild(e, a) } else e && (u = e.textContent, e.innerHTML = ""); var l = t.createEditSession(u), c = new o(new f(e), l, n), h = { document: l, editor: c, onResize: c.resize.bind(c, null) }; return a && (h.textarea = a), i.addListener(window, "resize", h.onResize), c.on("destroy", function () { i.removeListener(window, "resize", h.onResize), h.editor.container.env = null }), c.container.env = c.env = h, c }, t.createEditSession = function (e, t) { var n = new u(e, t); return n.setUndoManager(new a), n }, t.Range = s, t.Editor = o, t.EditSession = u, t.UndoManager = a, t.VirtualRenderer = f, t.version = t.config.version }); (function () { - window.require(["ace/ace"], function (a) { - if (a) { - a.config.init(true); - a.define = window.define; - } - if (!window.ace) - window.ace = a; - for (var key in a) if (a.hasOwnProperty(key)) - window.ace[key] = a[key]; - window.ace["default"] = window.ace; - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = window.ace; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js b/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js deleted file mode 100644 index cf28c3b012..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/ext/searchbox", ["require", "exports", "module", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/keyboard/hash_handler", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = e("../lib/lang"), s = e("../lib/event"), o = '.ace_search {background-color: #ddd;color: #666;border: 1px solid #cbcbcb;border-top: 0 none;overflow: hidden;margin: 0;padding: 4px 6px 0 4px;position: absolute;top: 0;z-index: 99;white-space: normal;}.ace_search.left {border-left: 0 none;border-radius: 0px 0px 5px 0px;left: 0;}.ace_search.right {border-radius: 0px 0px 0px 5px;border-right: 0 none;right: 0;}.ace_search_form, .ace_replace_form {margin: 0 20px 4px 0;overflow: hidden;line-height: 1.9;}.ace_replace_form {margin-right: 0;}.ace_search_form.ace_nomatch {outline: 1px solid red;}.ace_search_field {border-radius: 3px 0 0 3px;background-color: white;color: black;border: 1px solid #cbcbcb;border-right: 0 none;outline: 0;padding: 0;font-size: inherit;margin: 0;line-height: inherit;padding: 0 6px;min-width: 17em;vertical-align: top;min-height: 1.8em;box-sizing: content-box;}.ace_searchbtn {border: 1px solid #cbcbcb;line-height: inherit;display: inline-block;padding: 0 6px;background: #fff;border-right: 0 none;border-left: 1px solid #dcdcdc;cursor: pointer;margin: 0;position: relative;color: #666;}.ace_searchbtn:last-child {border-radius: 0 3px 3px 0;border-right: 1px solid #cbcbcb;}.ace_searchbtn:disabled {background: none;cursor: default;}.ace_searchbtn:hover {background-color: #eef1f6;}.ace_searchbtn.prev, .ace_searchbtn.next {padding: 0px 0.7em}.ace_searchbtn.prev:after, .ace_searchbtn.next:after {content: "";border: solid 2px #888;width: 0.5em;height: 0.5em;border-width: 2px 0 0 2px;display:inline-block;transform: rotate(-45deg);}.ace_searchbtn.next:after {border-width: 0 2px 2px 0 ;}.ace_searchbtn_close {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==) no-repeat 50% 0;border-radius: 50%;border: 0 none;color: #656565;cursor: pointer;font: 16px/16px Arial;padding: 0;height: 14px;width: 14px;top: 9px;right: 7px;position: absolute;}.ace_searchbtn_close:hover {background-color: #656565;background-position: 50% 100%;color: white;}.ace_button {margin-left: 2px;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;overflow: hidden;opacity: 0.7;border: 1px solid rgba(100,100,100,0.23);padding: 1px;box-sizing: border-box!important;color: black;}.ace_button:hover {background-color: #eee;opacity:1;}.ace_button:active {background-color: #ddd;}.ace_button.checked {border-color: #3399ff;opacity:1;}.ace_search_options{margin-bottom: 3px;text-align: right;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;clear: both;}.ace_search_counter {float: left;font-family: arial;padding: 0 8px;}', u = e("../keyboard/hash_handler").HashHandler, a = e("../lib/keys"), f = 999; r.importCssString(o, "ace_searchbox"); var l = function (e, t, n) { var i = r.createElement("div"); r.buildDom(["div", { "class": "ace_search right" }, ["span", { action: "hide", "class": "ace_searchbtn_close" }], ["div", { "class": "ace_search_form" }, ["input", { "class": "ace_search_field", placeholder: "Search for", spellcheck: "false" }], ["span", { action: "findPrev", "class": "ace_searchbtn prev" }, "\u200b"], ["span", { action: "findNext", "class": "ace_searchbtn next" }, "\u200b"], ["span", { action: "findAll", "class": "ace_searchbtn", title: "Alt-Enter" }, "All"]], ["div", { "class": "ace_replace_form" }, ["input", { "class": "ace_search_field", placeholder: "Replace with", spellcheck: "false" }], ["span", { action: "replaceAndFindNext", "class": "ace_searchbtn" }, "Replace"], ["span", { action: "replaceAll", "class": "ace_searchbtn" }, "All"]], ["div", { "class": "ace_search_options" }, ["span", { action: "toggleReplace", "class": "ace_button", title: "Toggle Replace mode", style: "float:left;margin-top:-2px;padding:0 5px;" }, "+"], ["span", { "class": "ace_search_counter" }], ["span", { action: "toggleRegexpMode", "class": "ace_button", title: "RegExp Search" }, ".*"], ["span", { action: "toggleCaseSensitive", "class": "ace_button", title: "CaseSensitive Search" }, "Aa"], ["span", { action: "toggleWholeWords", "class": "ace_button", title: "Whole Word Search" }, "\\b"], ["span", { action: "searchInSelection", "class": "ace_button", title: "Search In Selection" }, "S"]]], i), this.element = i.firstChild, this.setSession = this.setSession.bind(this), this.$init(), this.setEditor(e), r.importCssString(o, "ace_searchbox", e.container) }; (function () { this.setEditor = function (e) { e.searchBox = this, e.renderer.scroller.appendChild(this.element), this.editor = e }, this.setSession = function (e) { this.searchRange = null, this.$syncOptions(!0) }, this.$initElements = function (e) { this.searchBox = e.querySelector(".ace_search_form"), this.replaceBox = e.querySelector(".ace_replace_form"), this.searchOption = e.querySelector("[action=searchInSelection]"), this.replaceOption = e.querySelector("[action=toggleReplace]"), this.regExpOption = e.querySelector("[action=toggleRegexpMode]"), this.caseSensitiveOption = e.querySelector("[action=toggleCaseSensitive]"), this.wholeWordOption = e.querySelector("[action=toggleWholeWords]"), this.searchInput = this.searchBox.querySelector(".ace_search_field"), this.replaceInput = this.replaceBox.querySelector(".ace_search_field"), this.searchCounter = e.querySelector(".ace_search_counter") }, this.$init = function () { var e = this.element; this.$initElements(e); var t = this; s.addListener(e, "mousedown", function (e) { setTimeout(function () { t.activeInput.focus() }, 0), s.stopPropagation(e) }), s.addListener(e, "click", function (e) { var n = e.target || e.srcElement, r = n.getAttribute("action"); r && t[r] ? t[r]() : t.$searchBarKb.commands[r] && t.$searchBarKb.commands[r].exec(t), s.stopPropagation(e) }), s.addCommandKeyListener(e, function (e, n, r) { var i = a.keyCodeToString(r), o = t.$searchBarKb.findKeyCommand(n, i); o && o.exec && (o.exec(t), s.stopEvent(e)) }), this.$onChange = i.delayedCall(function () { t.find(!1, !1) }), s.addListener(this.searchInput, "input", function () { t.$onChange.schedule(20) }), s.addListener(this.searchInput, "focus", function () { t.activeInput = t.searchInput, t.searchInput.value && t.highlight() }), s.addListener(this.replaceInput, "focus", function () { t.activeInput = t.replaceInput, t.searchInput.value && t.highlight() }) }, this.$closeSearchBarKb = new u([{ bindKey: "Esc", name: "closeSearchBar", exec: function (e) { e.searchBox.hide() } }]), this.$searchBarKb = new u, this.$searchBarKb.bindKeys({ "Ctrl-f|Command-f": function (e) { var t = e.isReplace = !e.isReplace; e.replaceBox.style.display = t ? "" : "none", e.replaceOption.checked = !1, e.$syncOptions(), e.searchInput.focus() }, "Ctrl-H|Command-Option-F": function (e) { if (e.editor.getReadOnly()) return; e.replaceOption.checked = !0, e.$syncOptions(), e.replaceInput.focus() }, "Ctrl-G|Command-G": function (e) { e.findNext() }, "Ctrl-Shift-G|Command-Shift-G": function (e) { e.findPrev() }, esc: function (e) { setTimeout(function () { e.hide() }) }, Return: function (e) { e.activeInput == e.replaceInput && e.replace(), e.findNext() }, "Shift-Return": function (e) { e.activeInput == e.replaceInput && e.replace(), e.findPrev() }, "Alt-Return": function (e) { e.activeInput == e.replaceInput && e.replaceAll(), e.findAll() }, Tab: function (e) { (e.activeInput == e.replaceInput ? e.searchInput : e.replaceInput).focus() } }), this.$searchBarKb.addCommands([{ name: "toggleRegexpMode", bindKey: { win: "Alt-R|Alt-/", mac: "Ctrl-Alt-R|Ctrl-Alt-/" }, exec: function (e) { e.regExpOption.checked = !e.regExpOption.checked, e.$syncOptions() } }, { name: "toggleCaseSensitive", bindKey: { win: "Alt-C|Alt-I", mac: "Ctrl-Alt-R|Ctrl-Alt-I" }, exec: function (e) { e.caseSensitiveOption.checked = !e.caseSensitiveOption.checked, e.$syncOptions() } }, { name: "toggleWholeWords", bindKey: { win: "Alt-B|Alt-W", mac: "Ctrl-Alt-B|Ctrl-Alt-W" }, exec: function (e) { e.wholeWordOption.checked = !e.wholeWordOption.checked, e.$syncOptions() } }, { name: "toggleReplace", exec: function (e) { e.replaceOption.checked = !e.replaceOption.checked, e.$syncOptions() } }, { name: "searchInSelection", exec: function (e) { e.searchOption.checked = !e.searchRange, e.setSearchRange(e.searchOption.checked && e.editor.getSelectionRange()), e.$syncOptions() } }]), this.setSearchRange = function (e) { this.searchRange = e, e ? this.searchRangeMarker = this.editor.session.addMarker(e, "ace_active-line") : this.searchRangeMarker && (this.editor.session.removeMarker(this.searchRangeMarker), this.searchRangeMarker = null) }, this.$syncOptions = function (e) { r.setCssClass(this.replaceOption, "checked", this.searchRange), r.setCssClass(this.searchOption, "checked", this.searchOption.checked), this.replaceOption.textContent = this.replaceOption.checked ? "-" : "+", r.setCssClass(this.regExpOption, "checked", this.regExpOption.checked), r.setCssClass(this.wholeWordOption, "checked", this.wholeWordOption.checked), r.setCssClass(this.caseSensitiveOption, "checked", this.caseSensitiveOption.checked); var t = this.editor.getReadOnly(); this.replaceOption.style.display = t ? "none" : "", this.replaceBox.style.display = this.replaceOption.checked && !t ? "" : "none", this.find(!1, !1, e) }, this.highlight = function (e) { this.editor.session.highlight(e || this.editor.$search.$options.re), this.editor.renderer.updateBackMarkers() }, this.find = function (e, t, n) { var i = this.editor.find(this.searchInput.value, { skipCurrent: e, backwards: t, wrap: !0, regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked, preventScroll: n, range: this.searchRange }), s = !i && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", s), this.editor._emit("findSearchBox", { match: !s }), this.highlight(), this.updateCounter() }, this.updateCounter = function () { var e = this.editor, t = e.$search.$options.re, n = 0, r = 0; if (t) { var i = this.searchRange ? e.session.getTextRange(this.searchRange) : e.getValue(), s = e.session.doc.positionToIndex(e.selection.anchor); this.searchRange && (s -= e.session.doc.positionToIndex(this.searchRange.start)); var o = t.lastIndex = 0, u; while (u = t.exec(i)) { n++, o = u.index, o <= s && r++; if (n > f) break; if (!u[0]) { t.lastIndex = o += 1; if (o >= i.length) break } } } this.searchCounter.textContent = r + " of " + (n > f ? f + "+" : n) }, this.findNext = function () { this.find(!0, !1) }, this.findPrev = function () { this.find(!0, !0) }, this.findAll = function () { var e = this.editor.findAll(this.searchInput.value, { regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked }), t = !e && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", t), this.editor._emit("findSearchBox", { match: !t }), this.highlight(), this.hide() }, this.replace = function () { this.editor.getReadOnly() || this.editor.replace(this.replaceInput.value) }, this.replaceAndFindNext = function () { this.editor.getReadOnly() || (this.editor.replace(this.replaceInput.value), this.findNext()) }, this.replaceAll = function () { this.editor.getReadOnly() || this.editor.replaceAll(this.replaceInput.value) }, this.hide = function () { this.active = !1, this.setSearchRange(null), this.editor.off("changeSession", this.setSession), this.element.style.display = "none", this.editor.keyBinding.removeKeyboardHandler(this.$closeSearchBarKb), this.editor.focus() }, this.show = function (e, t) { this.active = !0, this.editor.on("changeSession", this.setSession), this.element.style.display = "", this.replaceOption.checked = t, e && (this.searchInput.value = e), this.searchInput.focus(), this.searchInput.select(), this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb), this.$syncOptions(!0) }, this.isFocused = function () { var e = document.activeElement; return e == this.searchInput || e == this.replaceInput } }).call(l.prototype), t.SearchBox = l, t.Search = function (e, t) { var n = e.searchBox || new l(e); n.show(e.session.getTextRange(), t) } }); (function () { - window.require(["ace/ext/searchbox"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js b/esphome/dashboard/static/js/vendor/ace/mode-yaml.js deleted file mode 100644 index 81cf343aa5..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/mode/yaml_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = function () { this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "list.markup", regex: /^(?:-{3}|\.{3})\s*(?=#|$)/ }, { token: "list.markup", regex: /^\s*[\-?](?:$|\s)/ }, { token: "constant", regex: "!![\\w//]+" }, { token: "constant.language", regex: "[&\\*][a-zA-Z0-9-_]+" }, { token: ["meta.tag", "keyword"], regex: /^(\s*\w.*?)(:(?=\s|$))/ }, { token: ["meta.tag", "keyword"], regex: /(\w+?)(\s*:(?=\s|$))/ }, { token: "keyword.operator", regex: "<<\\w*:\\w*" }, { token: "keyword.operator", regex: "-\\s*(?=[{])" }, { token: "string", regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' }, { token: "string", regex: /[|>][-+\d]*(?:$|\s+(?:$|#))/, onMatch: function (e, t, n, r) { r = r.replace(/ #.*/, ""); var i = /^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/, "").length, s = parseInt(/\d+[\s+-]*$/.exec(r)); return s ? (i += s - 1, this.next = "mlString") : this.next = "mlStringPre", n.length ? (n[0] = this.next, n[1] = i) : (n.push(this.next), n.push(i)), this.token }, next: "mlString" }, { token: "string", regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" }, { token: "constant.numeric", regex: /(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/ }, { token: "constant.numeric", regex: /[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/ }, { token: "constant.language.boolean", regex: "\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b" }, { token: "paren.lparen", regex: "[[({]" }, { token: "paren.rparen", regex: "[\\])}]" }, { token: "text", regex: /[^\s,:\[\]\{\}]+/ }], mlStringPre: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.shift(), n.shift()) : (n[1] = e.length - 1, this.next = n[0] = "mlString"), this.token }, next: "mlString" }, { defaultToken: "string" }], mlString: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.splice(0)) : this.next = "mlString", this.token }, next: "mlString" }, { token: "string", regex: ".+" }] }, this.normalizeRules() }; r.inherits(s, i), t.YamlHighlightRules = s }), define("ace/mode/matching_brace_outdent", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = function () { }; (function () { this.checkOutdent = function (e, t) { return /^\s+$/.test(e) ? /^\s*\}/.test(t) : !1 }, this.autoOutdent = function (e, t) { var n = e.getLine(t), i = n.match(/^(\s*\})/); if (!i) return 0; var s = i[1].length, o = e.findMatchingBracket({ row: t, column: s }); if (!o || o.row == t) return 0; var u = this.$getIndent(e.getLine(o.row)); e.replace(new r(t, 0, t, s - 1), u) }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] } }).call(i.prototype), t.MatchingBraceOutdent = i }), define("ace/mode/folding/coffee", ["require", "exports", "module", "ace/lib/oop", "ace/mode/folding/fold_mode", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("./fold_mode").FoldMode, s = e("../../range").Range, o = t.FoldMode = function () { }; r.inherits(o, i), function () { this.getFoldWidgetRange = function (e, t, n) { var r = this.indentationBlock(e, n); if (r) return r; var i = /\S/, o = e.getLine(n), u = o.search(i); if (u == -1 || o[u] != "#") return; var a = o.length, f = e.getLength(), l = n, c = n; while (++n < f) { o = e.getLine(n); var h = o.search(i); if (h == -1) continue; if (o[h] != "#") break; c = n } if (c > l) { var p = e.getLine(c).length; return new s(l, a, c, p) } }, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n), i = r.search(/\S/), s = e.getLine(n + 1), o = e.getLine(n - 1), u = o.search(/\S/), a = s.search(/\S/); if (i == -1) return e.foldWidgets[n - 1] = u != -1 && u < a ? "start" : "", ""; if (u == -1) { if (i == a && r[i] == "#" && s[i] == "#") return e.foldWidgets[n - 1] = "", e.foldWidgets[n + 1] = "", "start" } else if (u == i && r[i] == "#" && o[i] == "#" && e.getLine(n - 2).search(/\S/) == -1) return e.foldWidgets[n - 1] = "start", e.foldWidgets[n + 1] = "", ""; return u != -1 && u < i ? e.foldWidgets[n - 1] = "start" : e.foldWidgets[n - 1] = "", i < a ? "start" : "" } }.call(o.prototype) }), define("ace/mode/yaml", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/yaml_highlight_rules", "ace/mode/matching_brace_outdent", "ace/mode/folding/coffee"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text").Mode, s = e("./yaml_highlight_rules").YamlHighlightRules, o = e("./matching_brace_outdent").MatchingBraceOutdent, u = e("./folding/coffee").FoldMode, a = function () { this.HighlightRules = s, this.$outdent = new o, this.foldingRules = new u, this.$behaviour = this.$defaultBehaviour }; r.inherits(a, i), function () { this.lineCommentStart = ["#"], this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t); if (e == "start") { var i = t.match(/^.*[\{\(\[]\s*$/); i && (r += n) } return r }, this.checkOutdent = function (e, t, n) { return this.$outdent.checkOutdent(t, n) }, this.autoOutdent = function (e, t, n) { this.$outdent.autoOutdent(t, n) }, this.$id = "ace/mode/yaml" }.call(a.prototype), t.Mode = a }); (function () { - window.require(["ace/mode/yaml"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js b/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js deleted file mode 100644 index 2335f6d773..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/theme/dreamweaver", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { t.isDark = !1, t.cssClass = "ace-dreamweaver", t.cssText = '.ace-dreamweaver .ace_gutter {background: #e8e8e8;color: #333;}.ace-dreamweaver .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-dreamweaver {background-color: #FFFFFF;color: black;}.ace-dreamweaver .ace_fold {background-color: #757AD8;}.ace-dreamweaver .ace_cursor {color: black;}.ace-dreamweaver .ace_invisible {color: rgb(191, 191, 191);}.ace-dreamweaver .ace_storage,.ace-dreamweaver .ace_keyword {color: blue;}.ace-dreamweaver .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-dreamweaver .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-dreamweaver .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-dreamweaver .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-dreamweaver .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_support.ace_type,.ace-dreamweaver .ace_support.ace_class {color: #009;}.ace-dreamweaver .ace_support.ace_php_tag {color: #f00;}.ace-dreamweaver .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-dreamweaver .ace_string {color: #00F;}.ace-dreamweaver .ace_comment {color: rgb(76, 136, 107);}.ace-dreamweaver .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-dreamweaver .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-dreamweaver .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-dreamweaver .ace_variable {color: #06F}.ace-dreamweaver .ace_xml-pe {color: rgb(104, 104, 91);}.ace-dreamweaver .ace_entity.ace_name.ace_function {color: #00F;}.ace-dreamweaver .ace_heading {color: rgb(12, 7, 255);}.ace-dreamweaver .ace_list {color:rgb(185, 6, 144);}.ace-dreamweaver .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-dreamweaver .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-dreamweaver .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-dreamweaver .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-dreamweaver .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-dreamweaver .ace_gutter-active-line {background-color : #DCDCDC;}.ace-dreamweaver .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-dreamweaver .ace_meta.ace_tag {color:#009;}.ace-dreamweaver .ace_meta.ace_tag.ace_anchor {color:#060;}.ace-dreamweaver .ace_meta.ace_tag.ace_form {color:#F90;}.ace-dreamweaver .ace_meta.ace_tag.ace_image {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_script {color:#900;}.ace-dreamweaver .ace_meta.ace_tag.ace_style {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_table {color:#099;}.ace-dreamweaver .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-dreamweaver .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}'; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }); (function () { - window.require(["ace/theme/dreamweaver"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js b/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js deleted file mode 100644 index 862a649869..0000000000 --- a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("

"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("'),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0
    '),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); diff --git a/esphome/dashboard/templates/index.html b/esphome/dashboard/templates/index.html deleted file mode 100644 index b10901f5d4..0000000000 --- a/esphome/dashboard/templates/index.html +++ /dev/null @@ -1,678 +0,0 @@ - - - - - - - Dashboard - ESPHome - - - - - - - - - - - - - - - {% if streamer_mode %} - - {% end %} - - - - - - -
    -
    - -
    - - {% for i, entry in enumerate(entries) %} -
    -
    - - {{ escape(entry.name) }} - - more_vert - - {% if 'web_server' in entry.loaded_integrations %} - launch - {% end %} - - {% if entry.update_available %} - system_update - {% end %} - - -
    - Filename: - - {{ escape(entry.filename) }} - -
    - - {% if entry.comment %} -
    - {{ escape(entry.comment) }} -
    - {% end %} - -
    -
    - Edit - Validate - Upload - Logs -
    - -
    - {% end %} - -
    - -
    - - {% if len(entries) == 0 %} -
    -
    Welcome to ESPHome
    -

    It looks like you don't yet have any Nodes configured.

    -

    Click on the pulsating button at the bottom right of the page to add a Node.

    -
    - - - {% end %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - {% if begin and len(entries) == 1 %} - - {% end %} - - - - diff --git a/esphome/dashboard/templates/login.html b/esphome/dashboard/templates/login.html deleted file mode 100644 index 14116484af..0000000000 --- a/esphome/dashboard/templates/login.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Login - ESPHome - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    -
    - - v{{ version }} - Dashboard Login -

    - {% if hassio %} - Login by entering your Home Assistant login credentials. - {% else %} - Login by entering your ESPHome login credentials. - {% end %} -

    - - {% if error is not None %} -
    - Error! - {{ escape(error) }} -
    - - - {% end %} - -
    - {% if has_username or hassio %} -
    -
    - person - - -
    -
    - {% end %} - -
    -
    - lock - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - - - -
    -
    - - - diff --git a/esphome/wizard.py b/esphome/wizard.py index 7d875b7dd2..0d912e4bbf 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -49,17 +49,6 @@ BASE_CONFIG = """esphome: platform: {platform} board: {board} -wifi: - ssid: "{ssid}" - password: "{psk}" - - # Enable fallback hotspot (captive portal) in case wifi connection fails - ap: - ssid: "{fallback_name}" - password: "{fallback_psk}" - -captive_portal: - # Enable logging logger: @@ -83,12 +72,43 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) - if kwargs["password"]: - config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format( - kwargs["password"] + # Configure API + if "password" in kwargs: + config += ' password: "{0}"\n'.format(kwargs["password"]) + + # Configure OTA + config += "\nota:\n" + if "ota_password" in kwargs: + config += ' password: "{0}"'.format(kwargs["ota_password"]) + elif "password" in kwargs: + config += ' password: "{0}"'.format(kwargs["password"]) + + # Configuring wifi + config += "\n\nwifi:\n" + + if "ssid" in kwargs: + config += """ ssid: "{ssid}" + password: "{psk}" +""".format( + **kwargs ) else: - config += "\nota:\n" + config += """ # ssid: "My SSID" + # password: "mypassword" + + networks: +""" + + config += """ + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "{fallback_name}" + password: "{fallback_psk}" + +captive_portal: +""".format( + **kwargs + ) return config @@ -97,9 +117,9 @@ def wizard_write(path, **kwargs): name = kwargs["name"] board = kwargs["board"] - kwargs["ssid"] = sanitize_double_quotes(kwargs["ssid"]) - kwargs["psk"] = sanitize_double_quotes(kwargs["psk"]) - kwargs["password"] = sanitize_double_quotes(kwargs["password"]) + for key in ("ssid", "psk", "password", "ota_password"): + if key in kwargs: + kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" diff --git a/requirements.txt b/requirements.txt index 5915c2963f..e7f69865da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 +esphome-dashboard==20210611.0 From f599c36272c448192ddfad0eb1b68c9b04dd2d8a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:03:09 +1200 Subject: [PATCH 009/285] Bump version to v1.19.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30df9abe17..f7ad39b95d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 75b524ddc470ef083fc1dc3032492361d080df2d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:07:43 +1200 Subject: [PATCH 010/285] Fix formatting from cherry-pick conflict --- .../esp32_ble_server/ble_characteristic.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 23b2693839..62775ab05f 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, From d8264166842a71af0c4d1a95894658b96b4bdb06 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:05:10 +1200 Subject: [PATCH 011/285] Allow no networks or AP to be set. (#1908) Co-authored-by: Paulus Schoutsen --- esphome/components/wifi/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c45e179bc4..851c5f1b90 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -157,9 +157,7 @@ def _validate(config): config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): - raise cv.Invalid( - "Please specify at least an SSID or an Access Point " "to create." - ) + config[CONF_NETWORKS] = [] if config.get(CONF_FAST_CONNECT, False): networks = config.get(CONF_NETWORKS, []) From 106f0d611f1bcfa14b9da79e753ecf7441dab88a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 13:16:43 +1200 Subject: [PATCH 012/285] Validate that either networks, ap, or improv is set up (#1910) --- esphome/components/wifi/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 851c5f1b90..a701aa37e5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -137,6 +137,18 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) +def validate(config, item_config): + if ( + (CONF_NETWORKS in item_config) + and (item_config[CONF_NETWORKS] == []) + and (CONF_AP not in item_config) + ): + if "esp32_improv" not in config: + raise ValueError( + "Please specify at least an SSID or an Access Point to create." + ) + + def _validate(config): if CONF_PASSWORD in config and CONF_SSID not in config: raise cv.Invalid("Cannot have WiFi password without SSID!") From 744ca1af7c47f047f1414c31effe769079471e2b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 01:18:27 -0700 Subject: [PATCH 013/285] Bump frontend to 20210614.0 (#1912) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7f69865da..6ae20849da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210611.0 +esphome-dashboard==20210614.1 From 429caccefad7b747131c7511e7378c9ded829a12 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 15 Jun 2021 05:20:24 -0300 Subject: [PATCH 014/285] fixes compatibility with esphome cfg vscode (#1911) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9af08a8f21..c7b2151728 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -504,6 +504,7 @@ def parse_args(argv): "version", "clean", "dashboard", + "vscode", ], ) @@ -686,7 +687,7 @@ def run_esphome(argv): CORE.dashboard = args.dashboard setup_log(args.verbose, args.quiet) - if args.deprecated_argv_suggestion is not None: + if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " "and will be removed in the future. " From e8bdbc45a9f4abd976dd4fadb3f916b7160c49c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 20:26:53 +1200 Subject: [PATCH 015/285] Bump version to v1.19.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f7ad39b95d..a17c847953 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 28635124f9a6d57161ed328e9b1f57124eccbc01 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 14:04:17 -0700 Subject: [PATCH 016/285] Bump dashboard to 20210615.0 (#1918) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae20849da..33436fb1b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210614.1 +esphome-dashboard==20210615.0 From 69e6cf2c0c887ed76abb56eb9a1925ffa9b2517b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:06:50 +1200 Subject: [PATCH 017/285] Bump version to v1.19.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17c847953..b9a22d2613 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From a5383fd2088d2622b30cfa23e45733b936e20059 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:49:06 +1200 Subject: [PATCH 018/285] Shorten the ble name to prevent crash with long device names (#1920) --- esphome/components/esp32_ble/ble.cpp | 11 ++++++++++- esphome/core/application.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 18e80754df..d245cb9e4d 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -91,7 +91,16 @@ bool ESP32BLE::ble_setup_() { } } - err = esp_ble_gap_set_device_name(App.get_name().c_str()); + std::string name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } + } + + err = esp_ble_gap_set_device_name(name.c_str()); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err); return false; diff --git a/esphome/core/application.h b/esphome/core/application.h index aeda245161..97c99bd4f9 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -38,6 +38,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); } else { @@ -97,6 +98,8 @@ class Application { /// Get the name of this Application set by set_name(). const std::string &get_name() const { return this->name_; } + bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } + const std::string &get_compilation_time() const { return this->compilation_time_; } /** Set the target interval with which to run the loop() calls. @@ -245,6 +248,7 @@ class Application { std::string name_; std::string compilation_time_; + bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; int dump_config_at_{-1}; From fff3645901b760b10c6ed29f470a149b9963db87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:51:31 +1200 Subject: [PATCH 019/285] Bump version to v1.19.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b9a22d2613..abaf55dece 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0b6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 970563e07b997f2b6c7c5d5edc7a36a23be65af3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:00:51 +1200 Subject: [PATCH 020/285] Bump version to v1.19.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..3492858240 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From f763daa577cad745c98e51803e5dcf69d16a4ac4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Jun 2021 19:39:04 +0200 Subject: [PATCH 021/285] Fix update-all from dashboard (#1924) --- esphome/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c7b2151728..48f8bea083 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -394,7 +394,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration) + files = list_yaml_files(args.configuration[0]) twidth = 60 def print_bar(middle_text): @@ -408,7 +408,7 @@ def command_update_all(args): print("-" * twidth) print() rc = run_external_process( - "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" + "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f ) if rc == 0: print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) From f310ca1b748ca52b845ba04f6cd32bc794396c21 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 17 Jun 2021 05:40:55 +1200 Subject: [PATCH 022/285] Bump version to v1.19.0b7 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..53acf3d84e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0b7" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 066c1022d0fb7915569c957dbeb1c8c7e3517968 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jun 2021 12:35:54 -0700 Subject: [PATCH 023/285] Update dashboard to 20210617.0 (#1930) --- esphome/dashboard/dashboard.py | 28 +++++++++++++--------------- requirements.txt | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 90061a3d4e..00b12199c0 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler): self.redirect("./login") -_STATIC_FILE_HASHES = {} - - def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -741,19 +738,23 @@ def get_static_path(*args): return os.path.join(get_base_frontend_path(), "static", *args) +@functools.lru_cache(maxsize=None) def get_static_file_url(name): + base = f"./static/{name}" + + if ENV_DEV in os.environ: + return base + # Module imports can't deduplicate if stuff added to url if name == "js/esphome/index.js": - return f"./static/{name}" + import esphome_dashboard - if name in _STATIC_FILE_HASHES: - hash_ = _STATIC_FILE_HASHES[name] - else: - path = get_static_path(name) - with open(path, "rb") as f_handle: - hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] - _STATIC_FILE_HASHES[name] = hash_ - return f"./static/{name}?hash={hash_}" + return base.replace("index.js", esphome_dashboard.entrypoint()) + + path = get_static_path(name) + with open(path, "rb") as f_handle: + hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] + return f"{base}?hash={hash_}" def make_app(debug=get_bool_env(ENV_DEV)): @@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)): **app_settings, ) - if debug: - _STATIC_FILE_HASHES.clear() - return app diff --git a/requirements.txt b/requirements.txt index 33436fb1b1..f02d65e518 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210615.0 +esphome-dashboard==20210617.1 From a045d001bf6101a63f4ef815449ec94c5b0fdf1e Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 17 Jun 2021 23:39:13 +0400 Subject: [PATCH 024/285] Fix: midea_ac: fixed query status frame (#1922) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index e90155bad3..a29345035b 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, + 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From f1dcf0f0b8266b5e8f12a069bd254479d189e36f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Jun 2021 21:54:14 +0200 Subject: [PATCH 025/285] Improve config final validation (#1917) --- esphome/components/cse7766/sensor.py | 9 +- esphome/components/dfplayer/__init__.py | 9 +- esphome/components/gps/__init__.py | 5 +- esphome/components/http_request/__init__.py | 68 +++++++-------- esphome/components/rc522_spi/__init__.py | 9 +- esphome/components/rtttl/__init__.py | 24 +++-- esphome/components/sim800l/__init__.py | 7 +- esphome/components/spi/__init__.py | 28 ++++-- esphome/components/uart/__init__.py | 97 +++++++++++++-------- esphome/components/wifi/__init__.py | 22 ++--- esphome/config.py | 76 ++++++++++------ esphome/config_validation.py | 8 +- esphome/core/__init__.py | 10 +-- esphome/cpp_helpers.py | 3 +- esphome/final_validate.py | 57 ++++++++++++ esphome/loader.py | 10 ++- esphome/storage_json.py | 5 +- esphome/types.py | 18 ++++ 18 files changed, 303 insertions(+), 162 deletions(-) create mode 100644 esphome/final_validate.py create mode 100644 esphome/types.py diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 98cf4da96d..4ccb346efd 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -45,6 +45,9 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("60s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7766", baud_rate=4800, require_rx=True +) async def to_code(config): @@ -64,9 +67,3 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) - - -def validate(config, item_config): - uart.validate_device( - "cse7766", config, item_config, baud_rate=4800, require_tx=False - ) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 6af83888ab..3cdfc8ab85 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All( } ).extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "dfplayer", baud_rate=9600, require_tx=True +) async def to_code(config): @@ -80,12 +83,6 @@ async def to_code(config): await automation.build_automation(trigger, [], conf) -def validate(config, item_config): - uart.validate_device( - "dfplayer", config, item_config, baud_rate=9600, require_rx=False - ) - - @automation.register_action( "dfplayer.play_next", NextAction, diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index de3eae1115..2867aa7325 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -62,6 +62,7 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("20s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) async def to_code(config): @@ -95,7 +96,3 @@ async def to_code(config): # https://platformio.org/lib/show/1655/TinyGPSPlus cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict - - -def validate(config, item_config): - uart.validate_device("gps", config, item_config, require_tx=False) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 650602150a..f4475060df 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,6 +2,7 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -14,7 +15,6 @@ from esphome.const import ( CONF_URL, ) from esphome.core import CORE, Lambda -from esphome.core.config import PLATFORMIO_ESP8266_LUT DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -36,29 +36,6 @@ CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" -def validate_framework(config): - if CORE.is_esp32: - return config - - version = "RECOMMENDED" - if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]: - version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - - if version in ["LATEST", "DEV"]: - return config - - framework = ( - PLATFORMIO_ESP8266_LUT[version] - if version in PLATFORMIO_ESP8266_LUT - else version - ) - if framework < ARDUINO_VERSION_ESP8266["2.5.1"]: - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1" - ) - return config - - def validate_url(value): value = cv.string(value) try: @@ -92,19 +69,36 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional( - CONF_TIMEOUT, default="5s" - ): cv.positive_time_period_milliseconds, - } - ) - .add_extra(validate_framework) - .extend(cv.COMPONENT_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def validate_framework(config): + if CORE.is_esp32: + return + + # only for ESP8266 + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + version: str = fv.full_config.get().get_config_for_path(path) + + reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} + framework_version = reverse_map.get(version) + if framework_version is None or framework_version == "dev": + return + + if framework_version < "2.5.1": + raise cv.Invalid( + "This component is not supported on arduino framework version below 2.5.1", + path=[cv.ROOT_CONFIG_PATH] + path, + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) async def to_code(config): diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 68b1e64145..77b0a99662 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All( ).extend(spi.spi_device_schema(cs_pin_required=True)) ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "rc522_spi", require_miso=True, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await rc522.setup_rc522(var, config) await spi.register_spi_device(var, config) - - -def validate(config, item_config): - # validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi - spi.validate_device("rc522_spi", config, item_config, True, True) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 7f860fe3d7..e9453896ac 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -1,6 +1,7 @@ import logging import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.components.output import FloatOutput from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID @@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate(config, item_config): - # Not adding this to FloatOutput as this is the only component which needs `update_frequency` - - parent_config = config.get_config_by_id(item_config[CONF_OUTPUT]) - platform = parent_config[CONF_PLATFORM] - +def validate_parent_output_config(value): + platform = value.get(CONF_PLATFORM) PWM_GOOD = ["esp8266_pwm", "ledc"] PWM_BAD = [ "ac_dimmer ", @@ -55,14 +52,25 @@ def validate(config, item_config): ] if platform in PWM_BAD: - raise ValueError(f"Component rtttl cannot use {platform} as output component") + raise cv.Invalid(f"Component rtttl cannot use {platform} as output component") if platform not in PWM_GOOD: _LOGGER.warning( - "Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method." + "Component rtttl is not known to work with the selected output type. " + "Make sure this output supports custom frequency output method." ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema( + validate_parent_output_config + ) + }, + extra=cv.ALLOW_EXTRA, +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 40c011a769..0887b8640f 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "sim800l", baud_rate=9600, require_tx=True, require_rx=True +) async def to_code(config): @@ -54,10 +57,6 @@ async def to_code(config): ) -def validate(config, item_config): - uart.validate_device("sim800l", config, item_config, baud_rate=9600) - - SIM800L_SEND_SMS_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(Sim800LComponent), diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e6e073c4a4..803a45814c 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -69,9 +70,24 @@ async def register_spi_device(var, config): cg.add(var.set_cs_pin(pin)) -def validate_device(name, config, item_config, require_mosi, require_miso): - spi_config = config.get_config_by_id(item_config[CONF_SPI_ID]) - if require_mosi and CONF_MISO_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare miso_pin") - if require_miso and CONF_MOSI_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare mosi_pin") +def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): + hub_schema = {} + if require_miso: + hub_schema[ + cv.Required( + CONF_MISO_PIN, + msg=f"Component {name} requires this spi bus to declare a miso_pin", + ) + ] = cv.valid + if require_mosi: + hub_schema[ + cv.Required( + CONF_MOSI_PIN, + msg=f"Component {name} requires this spi bus to declare a mosi_pin", + ) + ] = cv.valid + + return cv.Schema( + {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, + extra=cv.ALLOW_EXTRA, + ) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index aaed333e34..d2fcac2cb6 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,5 +1,8 @@ +from typing import Optional + import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -92,42 +95,6 @@ async def to_code(config): cg.add(var.set_parity(config[CONF_PARITY])) -def validate_device( - name, config, item_config, baud_rate=None, require_tx=True, require_rx=True -): - if not hasattr(config, "uart_devices"): - config.uart_devices = {} - devices = config.uart_devices - - uart_config = config.get_config_by_id(item_config[CONF_UART_ID]) - - uart_id = uart_config[CONF_ID] - device = devices.setdefault(uart_id, {}) - - if require_tx: - if CONF_TX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare tx_pin") - if CONF_TX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it" - ) - device[CONF_TX_PIN] = name - - if require_rx: - if CONF_RX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare rx_pin") - if CONF_RX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it" - ) - device[CONF_RX_PIN] = name - - if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate: - raise ValueError( - f"Component {name} requires parent uart baud rate be {baud_rate}" - ) - - # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( { @@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema( } ) +KEY_UART_DEVICES = "uart_devices" + + +def final_validate_device_schema( + name: str, + *, + baud_rate: Optional[int] = None, + require_tx: bool = False, + require_rx: bool = False, +): + def validate_baud_rate(value): + if value != baud_rate: + raise cv.Invalid( + f"Component {name} required baud rate {baud_rate} for the uart bus" + ) + return value + + def validate_pin(opt, device): + def validator(value): + if opt in device: + raise cv.Invalid( + f"The uart {opt} is used both by {name} and {device[opt]}, " + f"but can only be used by one. Please create a new uart bus for {name}." + ) + device[opt] = name + return value + + return validator + + def validate_hub(hub_config): + hub_schema = {} + uart_id = hub_config[CONF_ID] + devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) + device = devices.setdefault(uart_id, {}) + + if require_tx: + hub_schema[ + cv.Required( + CONF_TX_PIN, + msg=f"Component {name} requires this uart bus to declare a tx_pin", + ) + ] = validate_pin(CONF_TX_PIN, device) + if require_rx: + hub_schema[ + cv.Required( + CONF_RX_PIN, + msg=f"Component {name} requires this uart bus to declare a rx_pin", + ) + ] = validate_pin(CONF_RX_PIN, device) + if baud_rate is not None: + hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config) + + return cv.Schema( + {cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)}, + extra=cv.ALLOW_EXTRA, + ) + async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a701aa37e5..fa28eaffd4 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition from esphome.components.network import add_mdns_library @@ -137,16 +138,17 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) -def validate(config, item_config): - if ( - (CONF_NETWORKS in item_config) - and (item_config[CONF_NETWORKS] == []) - and (CONF_AP not in item_config) - ): - if "esp32_improv" not in config: - raise ValueError( - "Please specify at least an SSID or an Access Point to create." - ) +def final_validate(config): + has_sta = bool(config.get(CONF_NETWORKS, True)) + has_ap = CONF_AP in config + has_improv = "esp32_improv" in fv.full_config.get() + if (not has_sta) and (not has_ap) and (not has_improv): + raise cv.Invalid( + "Please specify at least an SSID or an Access Point to create." + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) def _validate(config): diff --git a/esphome/config.py b/esphome/config.py index fcd2fac90f..93413a009c 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -21,11 +21,13 @@ from esphome.helpers import indent from esphome.util import safe_print, OrderedDict from typing import List, Optional, Tuple, Union -from esphome.core import ConfigType from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore +import esphome.final_validate as fv +import esphome.config_validation as cv +from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) @@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other -class Config(OrderedDict): +class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() # A list of voluptuous errors @@ -65,6 +67,7 @@ class Config(OrderedDict): self.output_paths = [] # type: List[Tuple[ConfigPath, str]] # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + self._data = {} def add_error(self, error): # type: (vol.Invalid) -> None @@ -72,6 +75,12 @@ class Config(OrderedDict): for err in error.errors: self.add_error(err) return + if cv.ROOT_CONFIG_PATH in error.path: + # Root value means that the path before the root should be ignored + last_root = max( + i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH + ) + error.path = error.path[last_root + 1 :] self.errors.append(error) @contextmanager @@ -140,13 +149,16 @@ class Config(OrderedDict): return doc_range - def get_nested_item(self, path): - # type: (ConfigPath) -> ConfigType + def get_nested_item( + self, path: ConfigPathType, raise_error: bool = False + ) -> ConfigFragmentType: data = self for item_index in path: try: data = data[item_index] except (KeyError, IndexError, TypeError): + if raise_error: + raise return {} return data @@ -163,11 +175,20 @@ class Config(OrderedDict): part.append(item_index) return part - def get_config_by_id(self, id): + def get_path_for_id(self, id: core.ID): + """Return the config fragment where the given ID is declared.""" for declared_id, path in self.declare_ids: if declared_id.id == str(id): - return self.get_nested_item(path[:-1]) - return None + return path + raise KeyError(f"ID {id} not found in configuration") + + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + return self.get_nested_item(path, raise_error=True) + + @property + def data(self): + """Return temporary data used by final validation functions.""" + return self._data def iter_ids(config, path=None): @@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None from esphome.cpp_generator import MockObjClass from esphome.cpp_types import Component - declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]] searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] for id, path in iter_ids(result): if id.is_declaration: if id.id is not None: # Look for duplicate definitions - match = next((v for v in declare_ids if v[0].id == id.id), None) + match = next((v for v in result.declare_ids if v[0].id == id.id), None) if match is not None: opath = "->".join(str(v) for v in match[1]) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) continue - declare_ids.append((id, path)) + result.declare_ids.append((id, path)) else: searching_ids.append((id, path)) # Resolve default ids after manual IDs - for id, _ in declare_ids: - id.resolve([v[0].id for v in declare_ids]) + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): CORE.component_ids.add(id.id) @@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None for id, path in searching_ids: if id.id is not None: # manually declared - match = next((v[0] for v in declare_ids if v[0].id == id.id), None) + match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) if match is None or not match.is_manual: # No declared ID with this name import difflib @@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None ) # Find candidates matches = difflib.get_close_matches( - id.id, [v[0].id for v in declare_ids if v[0].is_manual] + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] ) if matches: matches_s = ", ".join(f'"{x}"' for x in matches) @@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None if id.id is None and id.type is not None: matches = [] - for v in declare_ids: + for v in result.declare_ids: if v[0] is None or not isinstance(v[0].type, MockObjClass): continue inherits = v[0].type.inherits_from(id.type) @@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None def recursive_check_replaceme(value): - import esphome.config_validation as cv - if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) if isinstance(value, dict): @@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions): # 7. Final validation if not result.errors: # Inter - components validation - for path, conf, comp in validate_queue: - if comp.config_schema is None: + token = fv.full_config.set(result) + + for path, _, comp in validate_queue: + if comp.final_validate_schema is None: continue - if callable(comp.validate): - try: - comp.validate(result, result.get_nested_item(path)) - except ValueError as err: - result.add_str_error(err, path) + conf = result.get_nested_item(path) + with result.catch_error(path): + comp.final_validate_schema(conf) + + fv.full_config.reset(token) return result @@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config): ) elif "extra keys not allowed" in str(ex): message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) - elif "required key not provided" in str(ex): - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + elif isinstance(ex, vol.RequiredFieldInvalid): + if ex.msg == "required key not provided": + message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + else: + # Required has set a custom error message + message += ex.msg else: message += humanize_error(config, ex) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2cdb6b0b76..7292cc3af5 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -75,6 +75,9 @@ Inclusive = vol.Inclusive ALLOW_EXTRA = vol.ALLOW_EXTRA UNDEFINED = vol.UNDEFINED RequiredFieldInvalid = vol.RequiredFieldInvalid +# this sentinel object can be placed in an 'Invalid' path to say +# the rest of the error path is relative to the root config path +ROOT_CONFIG_PATH = object() RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword @@ -218,8 +221,8 @@ class Required(vol.Required): - *not* the `config.get(CONF_)` syntax. """ - def __init__(self, key): - super().__init__(key) + def __init__(self, key, msg=None): + super().__init__(key, msg=msg) def check_not_templatable(value): @@ -1073,6 +1076,7 @@ def invalid(message): def valid(value): + """A validator that is always valid and returns the value as-is.""" return value diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 1841dfd8be..df98e1b150 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,7 +2,7 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from esphome.const import ( CONF_ARDUINO_VERSION, @@ -23,6 +23,7 @@ from esphome.util import OrderedDict if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement + from ..types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -462,9 +463,9 @@ class EsphomeCore: # The board that's used (for example nodemcuv2) self.board: Optional[str] = None # The full raw configuration - self.raw_config: Optional[ConfigType] = None + self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated - self.config: Optional[ConfigType] = None + self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) # This is a priority queue (with heapq) # Each item is a tuple of form: (-priority, unique number, task) @@ -752,6 +753,3 @@ class EnumValue: CORE = EsphomeCore() - -ConfigType = Dict[str, Any] -CoreType = EsphomeCore diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1c52f38e50..1d66eabf6c 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -8,7 +8,8 @@ from esphome.const import ( ) # pylint: disable=unused-import -from esphome.core import coroutine, ID, CORE, ConfigType +from esphome.core import coroutine, ID, CORE +from esphome.types import ConfigType from esphome.cpp_generator import RawExpression, add, get_variable from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry diff --git a/esphome/final_validate.py b/esphome/final_validate.py new file mode 100644 index 0000000000..50fdbaf3f4 --- /dev/null +++ b/esphome/final_validate.py @@ -0,0 +1,57 @@ +from abc import ABC, abstractmethod, abstractproperty +from typing import Dict, Any +import contextvars + +from esphome.types import ConfigFragmentType, ID, ConfigPathType +import esphome.config_validation as cv + + +class FinalValidateConfig(ABC): + @abstractproperty + def data(self) -> Dict[str, Any]: + """A dictionary that can be used by post validation functions to store + global data during the validation phase. Each component should store its + data under a unique key + """ + + @abstractmethod + def get_path_for_id(self, id: ID) -> ConfigPathType: + """Get the config path a given ID has been declared in. + + This is the location under the _validated_ config (for example, with cv.ensure_list applied) + Raises KeyError if the id was not declared in the configuration. + """ + + @abstractmethod + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + """Get the config fragment for the given global path. + + Raises KeyError if a key in the path does not exist. + """ + + +FinalValidateConfig.register(dict) + +# Context variable tracking the full config for some final validation functions. +full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVar( + "full_config" +) + + +def id_declaration_match_schema(schema): + """A final-validation schema function that applies a schema to the outer config fragment of an + ID declaration. + + This validator must be applied to ID values. + """ + if not isinstance(schema, cv.Schema): + schema = cv.Schema(schema, extra=cv.ALLOW_EXTRA) + + def validator(value): + fconf = full_config.get() + path = fconf.get_path_for_id(value)[:-1] + declaration_config = fconf.get_config_for_path(path) + with cv.prepend_path([cv.ROOT_CONFIG_PATH] + path): + return schema(declaration_config) + + return validator diff --git a/esphome/loader.py b/esphome/loader.py index d9d407d787..f74fc6367d 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -12,6 +12,7 @@ from pathlib import Path from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS import esphome.core.config from esphome.core import CORE +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -81,8 +82,13 @@ class ComponentManifest: return getattr(self.module, "CODEOWNERS", []) @property - def validate(self): - return getattr(self.module, "validate", None) + def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]: + """Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called + after the main validation. In that function checks across components can be made. + + Note that the function can't mutate the configuration - no changes are saved + """ + return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) @property def source_files(self) -> Dict[Path, SourceFile]: diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 6f81e0d96a..c0fbc6edf7 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -4,14 +4,13 @@ from datetime import datetime import json import logging import os +from typing import Any, Optional, List from esphome import const from esphome.core import CORE from esphome.helpers import write_file_if_changed -# pylint: disable=unused-import, wrong-import-order -from esphome.core import CoreType -from typing import Any, Optional, List +from esphome.types import CoreType _LOGGER = logging.getLogger(__name__) diff --git a/esphome/types.py b/esphome/types.py new file mode 100644 index 0000000000..6bbfb00ce6 --- /dev/null +++ b/esphome/types.py @@ -0,0 +1,18 @@ +"""This helper module tracks commonly used types in the esphome python codebase.""" +from typing import Dict, Union, List + +from esphome.core import ID, Lambda, EsphomeCore + +ConfigFragmentType = Union[ + str, + int, + float, + None, + Dict[Union[str, int], "ConfigFragmentType"], + List["ConfigFragmentType"], + ID, + Lambda, +] +ConfigType = Dict[str, ConfigFragmentType] +CoreType = EsphomeCore +ConfigPathType = Union[str, int] From dd875e7529ef57abbed6c1ad549226eef28864a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:48:40 +1200 Subject: [PATCH 026/285] Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) --- esphome/components/bang_bang/bang_bang_climate.cpp | 6 +++--- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_traits.cpp | 5 +++++ esphome/components/climate/climate_traits.h | 2 ++ esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 12 ++++++------ esphome/components/coolix/coolix.cpp | 8 ++++---- esphome/components/daikin/daikin.cpp | 6 +++--- .../components/fujitsu_general/fujitsu_general.cpp | 4 ++-- esphome/components/hitachi_ac344/hitachi_ac344.cpp | 4 ++-- esphome/components/midea_ac/midea_climate.cpp | 2 +- esphome/components/midea_ac/midea_frame.cpp | 4 ++-- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 10 +++++----- esphome/components/tcl112/tcl112.cpp | 4 ++-- esphome/components/thermostat/climate.py | 8 ++++---- .../components/thermostat/thermostat_climate.cpp | 13 +++++++++---- esphome/components/thermostat/thermostat_climate.h | 2 ++ esphome/components/toshiba/toshiba.cpp | 4 ++-- esphome/components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/whirlpool/whirlpool.cpp | 4 ++-- esphome/components/yashima/yashima.cpp | 4 ++-- 22 files changed, 62 insertions(+), 48 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 45d5174390..ea1442755d 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,7 +21,7 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } } @@ -41,7 +41,7 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(true); @@ -50,7 +50,7 @@ climate::ClimateTraits BangBangClimate::traits() { return traits; } void BangBangClimate::compute_state_() { - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { // in non-auto mode, switch directly to appropriate action // - HEAT mode -> HEATING action // - COOL mode -> COOLING action diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index cd69469692..3aa29be2fb 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -159,7 +159,7 @@ struct ClimateDeviceRestoreState { * * The entire state of the climate device is encoded in public properties of the base class (current_temperature, * mode etc). These are read-only for the user and rw for integrations. The reason these are public - * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...` + * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ class Climate : public Nameable { public: diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index eda4722fcb..774ada785f 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,6 +8,8 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const { switch (mode) { case CLIMATE_MODE_OFF: return true; + case CLIMATE_MODE_HEAT_COOL: + return this->supports_heat_cool_mode_; case CLIMATE_MODE_AUTO: return this->supports_auto_mode_; case CLIMATE_MODE_COOL: @@ -31,6 +33,9 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_ supports_two_point_target_temperature_ = supports_two_point_target_temperature; } void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } +void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { + supports_heat_cool_mode_ = supports_heat_cool_mode; +} void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f0a48ca308..f8e6f87306 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -46,6 +46,7 @@ class ClimateTraits { bool get_supports_two_point_target_temperature() const; void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); void set_supports_auto_mode(bool supports_auto_mode); + void set_supports_heat_cool_mode(bool supports_heat_cool_mode); void set_supports_cool_mode(bool supports_cool_mode); void set_supports_heat_mode(bool supports_heat_mode); void set_supports_fan_only_mode(bool supports_fan_only_mode); @@ -100,6 +101,7 @@ class ClimateTraits { bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; bool supports_auto_mode_{false}; + bool supports_heat_cool_mode_{false}; bool supports_cool_mode_{false}; bool supports_heat_mode_{false}; bool supports_fan_only_mode_{false}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 8f06ff2214..f88b2174ee 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,7 +9,7 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_dry_mode(this->supports_dry_); diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 983d33c0b1..d675883fcf 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() { send_swing_cmd_ = false; remote_state |= COMMAND_SWING; } else { - if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) { + if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { remote_state |= COMMAND_ON_AI; } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) { remote_state |= COMMAND_ON; @@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COMMAND_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COMMAND_AUTO; break; case climate::CLIMATE_MODE_DRY: @@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() { } } - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; // remote_state |= FAN_MODE_AUTO_DRY; } @@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COMMAND_MASK) == COMMAND_ON) { this->mode = climate::CLIMATE_MODE_COOL; } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { @@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) this->mode = climate::CLIMATE_MODE_DRY; else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { @@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; // Fan Speed - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index e50521a348..012744b3e9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COOLIX_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COOLIX_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() { } else { remote_state |= COOLIX_FAN_TEMP_CODE; } - if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) { this->fan_mode = climate::CLIMATE_FAN_AUTO; remote_state |= COOLIX_FAN_MODE_AUTO_DRY; } else { @@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) this->mode = climate::CLIMATE_MODE_HEAT; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) this->mode = climate::CLIMATE_MODE_DRY; @@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_COOL; // Fan Speed - if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO || + if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) this->fan_mode = climate::CLIMATE_FAN_AUTO; else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index e0ffd46387..dca3bffbac 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() { case climate::CLIMATE_MODE_HEAT: operating_mode |= DAIKIN_MODE_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: operating_mode |= DAIKIN_MODE_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() { switch (this->mode) { case climate::CLIMATE_MODE_FAN_ONLY: return 0x32; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_DRY: return 0xc0; default: @@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case DAIKIN_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case DAIKIN_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 8671f38e8e..2e93a98e52 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() { case climate::CLIMATE_MODE_FAN_ONLY: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; @@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { case FUJITSU_GENERAL_MODE_AUTO: default: // TODO: CLIMATE_MODE_10C is missing from esphome - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index b2798b608a..86f82d8bbb 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: set_mode_(HITACHI_AC344_MODE_HEAT); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: set_mode_(HITACHI_AC344_MODE_AUTO); break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case HITACHI_AC344_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case HITACHI_AC344_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index f98cf74ac1..481a6da54d 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,7 +167,7 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(true); traits.set_supports_dry_mode(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index a29345035b..0a09e86de7 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { return climate::CLIMATE_MODE_OFF; switch (this->pbuf_[12] >> 5) { case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_AUTO; + return climate::CLIMATE_MODE_HEAT_COOL; case MIDEA_MODE_COOL: return climate::CLIMATE_MODE_COOL; case MIDEA_MODE_DRY: @@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { void PropertiesFrame::set_mode(climate::ClimateMode mode) { uint8_t m; switch (mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: m = MIDEA_MODE_AUTO; break; case climate::CLIMATE_MODE_COOL: diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index dbc70af75c..c9d0ce842e 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state[6] = MITSUBISHI_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] = MITSUBISHI_AUTO; break; case climate::CLIMATE_MODE_OFF: diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 24fb0ec905..0423ab27fc 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,7 +20,7 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->target_temperature = this->default_target_temperature_; } } @@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { this->target_temperature = *call.get_target_temperature(); // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_AUTO) + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) this->handle_non_auto_mode_(); this->publish_state(); @@ -39,7 +39,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); traits.set_supports_cool_mode(this->supports_cool_()); traits.set_supports_heat_mode(this->supports_heat_()); @@ -121,14 +121,14 @@ void PIDClimate::update_pid_() { // keep autotuner instance so that subsequent dump_configs will print the long result message. } else { value = res.output; - if (mode != climate::CLIMATE_MODE_AUTO) { + if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!"); } } } } - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { this->handle_non_auto_mode_(); } else { this->write_output_(value); diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 91cec27094..6921bbd3c0 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] &= 0xF0; remote_state[6] |= TCL112_AUTO; break; @@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case TCL112_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 4a371ec165..07a94fd184 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -227,7 +227,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) - auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config + heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config ) @@ -258,10 +258,10 @@ async def to_code(config): var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] ) - if auto_mode_available is True: - cg.add(var.set_supports_auto(True)) + if heat_cool_mode_available is True: + cg.add(var.set_supports_heat_cool(True)) else: - cg.add(var.set_supports_auto(False)) + cg.add(var.set_supports_heat_cool(False)) if CONF_COOL_ACTION in config: await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 3bab0e85fd..a96c702473 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -21,7 +21,7 @@ void ThermostatClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles temps for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } // refresh the climate action based on the restored settings @@ -79,6 +79,7 @@ climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_auto_mode(this->supports_auto_); + traits.set_supports_heat_cool_mode(this->supports_heat_cool_); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_dry_mode(this->supports_dry_); traits.set_supports_fan_only_mode(this->supports_fan_only_); @@ -130,7 +131,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_OFF: target_action = climate::CLIMATE_ACTION_OFF; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_HEAT: if (this->supports_cool_) { @@ -321,7 +322,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { case climate::CLIMATE_MODE_OFF: trig = this->off_mode_trigger_; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // trig = this->auto_mode_trigger_; break; case climate::CLIMATE_MODE_COOL: @@ -339,7 +340,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value - mode = climate::CLIMATE_MODE_AUTO; + mode = climate::CLIMATE_MODE_HEAT_COOL; // trig = this->auto_mode_trigger_; } assert(trig != nullptr); @@ -434,6 +435,9 @@ ThermostatClimate::ThermostatClimate() swing_mode_vertical_trigger_(new Trigger<>()) {} void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { + this->supports_heat_cool_ = supports_heat_cool; +} void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; } void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } @@ -521,6 +525,7 @@ void ThermostatClimate::dump_config() { } ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); + ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 86a1007efa..3fd482da53 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_hysteresis(float hysteresis); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); + void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); @@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value for any given attribute means that the controller has no such action /// (for example a thermostat, where only heating and not-heating is possible). bool supports_auto_{false}; + bool supports_heat_cool_{false}; bool supports_cool_{false}; bool supports_dry_{false}; bool supports_fan_only_{false}; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 33e2831dd3..b932516edf 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() { mode = TOSHIBA_MODE_COOL; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: mode = TOSHIBA_MODE_AUTO; } @@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { case TOSHIBA_MODE_AUTO: default: /* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */ - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } /* Get the target temperature */ diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 962d18a391..9c9cf9f3e7 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -13,7 +13,7 @@ void TuyaClimate::setup() { this->mode = climate::CLIMATE_MODE_OFF; if (datapoint.value_bool) { if (this->supports_heat_ && this->supports_cool_) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } else if (this->supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_) { diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index e7c93246f2..5a1e025a38 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() { this->powered_on_assumed = powered_on; } switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // set fan auto // set temp auto temp // set sleep false @@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case WHIRLPOOL_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e3c0a33127..e53e5fcccc 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,7 +82,7 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(false); @@ -139,7 +139,7 @@ void YashimaClimate::transmit_state_() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0; remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5; break; From 01904a0f107c92d55a51fba93f6c31a91b5259eb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:52:02 +1200 Subject: [PATCH 027/285] Bump version to v1.19.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3492858240..733931ce09 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 4fa959ba45bb6b81729ac0644d780b559bea5016 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Thu, 17 Jun 2021 21:58:39 -0500 Subject: [PATCH 028/285] Don't send Tuya commands while currently receiving a message (#1886) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 3f09db068d..d1f8b8c9e2 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !command_queue_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } From 8aec092ab62a9c6d4ba436a51071144006e29b39 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 21 Jun 2021 00:59:12 +0400 Subject: [PATCH 029/285] Fix midea_ac query frame (#1940) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 0a09e86de7..a624400411 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, - 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, + 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From d73a44c50435a108bdb64586dc46c719dd2f6406 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 09:00:16 +1200 Subject: [PATCH 030/285] Allow wifi setup to proceed when there is no sta or ap (#1931) --- esphome/components/wifi/wifi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e98754fac7..7d6cb1347e 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() { } bool WiFiComponent::can_proceed() { - if (this->has_ap() && !this->has_sta()) { + if (!this->has_sta()) { return true; } return this->is_connected(); From 969834e0375432b39df2dc32bbf0ce6ddeeb4a28 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:17:38 +1200 Subject: [PATCH 031/285] Fix bad climate control enum (#1942) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5a4492216e..88991ff795 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits") ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { "OFF": ClimateMode.CLIMATE_MODE_OFF, - "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL, + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, "COOL": ClimateMode.CLIMATE_MODE_COOL, "HEAT": ClimateMode.CLIMATE_MODE_HEAT, "DRY": ClimateMode.CLIMATE_MODE_DRY, From 2a5def10e74f6f831e9d784411348db8378c8bc2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:40:05 +1200 Subject: [PATCH 032/285] Bump version to v1.19.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 733931ce09..35959caaff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 89dfa5ea82d1cbb61cb80385e1c307153f50aea4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:15:11 +1200 Subject: [PATCH 033/285] Bump esphome-dashboard to 20210622.0 (#1955) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f02d65e518..12500d57b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210617.1 +esphome-dashboard==20210622.0 From 150114d774b36f6c7af56b187aa8b2a8624a5297 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 19:39:37 +1200 Subject: [PATCH 034/285] Bump version to v1.19.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 35959caaff..de33ebc2de 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From d4eb0f16550db1cda3573cee2ff76eda9aaa4d36 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 21:17:01 +0200 Subject: [PATCH 035/285] Rework climate traits (#1941) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 16 +- esphome/components/api/api_connection.cpp | 52 ++--- esphome/components/api/api_pb2.cpp | 40 ++-- esphome/components/api/api_pb2.h | 12 +- .../bang_bang/bang_bang_climate.cpp | 23 +- esphome/components/climate/automation.h | 4 +- esphome/components/climate/climate.cpp | 74 +++--- esphome/components/climate/climate.h | 8 +- esphome/components/climate/climate_mode.cpp | 4 +- esphome/components/climate/climate_mode.h | 9 +- esphome/components/climate/climate_traits.cpp | 203 ---------------- esphome/components/climate/climate_traits.h | 219 +++++++++++------- esphome/components/climate_ir/climate_ir.cpp | 65 +----- esphome/components/climate_ir/climate_ir.h | 9 +- esphome/components/daikin/daikin.h | 11 +- .../components/hitachi_ac344/hitachi_ac344.h | 9 +- esphome/components/midea_ac/midea_climate.cpp | 48 ++-- esphome/components/midea_ac/midea_climate.h | 18 +- esphome/components/mqtt/mqtt_climate.cpp | 14 +- esphome/components/pid/pid_climate.cpp | 10 +- .../thermostat/thermostat_climate.cpp | 73 ++++-- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/yashima/yashima.cpp | 11 +- script/api_protobuf/api_protobuf.py | 1 + 24 files changed, 393 insertions(+), 546 deletions(-) mode change 100644 => 100755 script/api_protobuf/api_protobuf.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bdb94b3d9b..87a7cf4749 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,11 +710,11 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_ECO = 0; + CLIMATE_PRESET_HOME = 0; CLIMATE_PRESET_AWAY = 1; CLIMATE_PRESET_BOOST = 2; CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_HOME = 4; + CLIMATE_PRESET_ECO = 4; CLIMATE_PRESET_SLEEP = 5; CLIMATE_PRESET_ACTIVITY = 6; } @@ -734,7 +734,9 @@ message ListEntitiesClimateResponse { float visual_min_temperature = 8; float visual_max_temperature = 9; float visual_temperature_step = 10; - bool supports_away = 11; + // for older peer versions - in new system this + // is if CLIMATE_PRESET_AWAY exists is supported_presets + bool legacy_supports_away = 11; bool supports_action = 12; repeated ClimateFanMode supported_fan_modes = 13; repeated ClimateSwingMode supported_swing_modes = 14; @@ -754,7 +756,8 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - bool away = 7; + // For older peers, equal to preset == CLIMATE_PRESET_AWAY + bool legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -777,8 +780,9 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - bool has_away = 10; - bool away = 11; + // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset + bool has_legacy_away = 10; + bool legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 175c3c4487..68187f259e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { } else { resp.target_temperature = climate->target_temperature; } - if (traits.get_supports_away()) - resp.away = climate->away; if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) resp.custom_fan_mode = climate->custom_fan_mode.value(); - if (traits.get_supports_presets() && climate->preset.has_value()) + if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); + resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; + } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); if (traits.get_supports_swing_modes()) @@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); - for (auto mode : - {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) { - if (traits.supports_mode(mode)) - msg.supported_modes.push_back(static_cast(mode)); - } + + for (auto mode : traits.get_supported_modes()) + msg.supported_modes.push_back(static_cast(mode)); + msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_temperature_step = traits.get_visual_temperature_step(); - msg.supports_away = traits.get_supports_away(); + msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); msg.supports_action = traits.get_supports_action(); - for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, - climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) { - if (traits.supports_fan_mode(fan_mode)) - msg.supported_fan_modes.push_back(static_cast(fan_mode)); - } - for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) { + + for (auto fan_mode : traits.get_supported_fan_modes()) + msg.supported_fan_modes.push_back(static_cast(fan_mode)); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) msg.supported_custom_fan_modes.push_back(custom_fan_mode); - } - for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP, - climate::CLIMATE_PRESET_ACTIVITY}) { - if (traits.supports_preset(preset)) - msg.supported_presets.push_back(static_cast(preset)); - } - for (auto const &custom_preset : traits.get_supported_custom_presets()) { + for (auto preset : traits.get_supported_presets()) + msg.supported_presets.push_back(static_cast(preset)); + for (auto const &custom_preset : traits.get_supported_custom_presets()) msg.supported_custom_presets.push_back(custom_preset); - } - for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL}) { - if (traits.supports_swing_mode(swing_mode)) - msg.supported_swing_modes.push_back(static_cast(swing_mode)); - } + for (auto swing_mode : traits.get_supported_swing_modes()) + msg.supported_swing_modes.push_back(static_cast(swing_mode)); return this->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { @@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_away) - call.set_away(msg.away); + if (msg.has_legacy_away) + call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a9e9d64bc1..1e023f3988 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,16 +192,16 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { - case enums::CLIMATE_PRESET_ECO: - return "CLIMATE_PRESET_ECO"; + case enums::CLIMATE_PRESET_HOME: + return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: return "CLIMATE_PRESET_AWAY"; case enums::CLIMATE_PRESET_BOOST: return "CLIMATE_PRESET_BOOST"; case enums::CLIMATE_PRESET_COMFORT: return "CLIMATE_PRESET_COMFORT"; - case enums::CLIMATE_PRESET_HOME: - return "CLIMATE_PRESET_HOME"; + case enums::CLIMATE_PRESET_ECO: + return "CLIMATE_PRESET_ECO"; case enums::CLIMATE_PRESET_SLEEP: return "CLIMATE_PRESET_SLEEP"; case enums::CLIMATE_PRESET_ACTIVITY: @@ -2672,7 +2672,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v return true; } case 11: { - this->supports_away = value.as_bool(); + this->legacy_supports_away = value.as_bool(); return true; } case 12: { @@ -2756,7 +2756,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(10, this->visual_temperature_step); - buffer.encode_bool(11, this->supports_away); + buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(12, this->supports_action); for (auto &it : this->supported_fan_modes) { buffer.encode_enum(13, it, true); @@ -2823,8 +2823,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" supports_away: "); - out.append(YESNO(this->supports_away)); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); out.append("\n"); out.append(" supports_action: "); @@ -2869,7 +2869,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 8: { @@ -2939,7 +2939,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->away); + buffer.encode_bool(7, this->legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -2979,8 +2979,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" action: "); @@ -3031,11 +3031,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_away = value.as_bool(); + this->has_legacy_away = value.as_bool(); return true; } case 11: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 12: { @@ -3120,8 +3120,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_away); - buffer.encode_bool(11, this->away); + buffer.encode_bool(10, this->has_legacy_away); + buffer.encode_bool(11, this->legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3176,12 +3176,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_away: "); - out.append(YESNO(this->has_away)); + out.append(" has_legacy_away: "); + out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 04d5834572..7f37c0b94b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,11 +90,11 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_ECO = 0, + CLIMATE_PRESET_HOME = 0, CLIMATE_PRESET_AWAY = 1, CLIMATE_PRESET_BOOST = 2, CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_HOME = 4, + CLIMATE_PRESET_ECO = 4, CLIMATE_PRESET_SLEEP = 5, CLIMATE_PRESET_ACTIVITY = 6, }; @@ -709,7 +709,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { float visual_min_temperature{0.0f}; float visual_max_temperature{0.0f}; float visual_temperature_step{0.0f}; - bool supports_away{false}; + bool legacy_supports_away{false}; bool supports_action{false}; std::vector supported_fan_modes{}; std::vector supported_swing_modes{}; @@ -732,7 +732,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool away{false}; + bool legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -758,8 +758,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_away{false}; - bool away{false}; + bool has_legacy_away{false}; + bool legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index ea1442755d..c915d69981 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) - this->change_away_(*call.get_away()); + if (call.get_preset().has_value()) + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); this->compute_state_(); this->publish_state(); @@ -41,11 +41,20 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + }); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); traits.set_supports_two_point_target_temperature(true); - traits.set_supports_away(this->supports_away_); + if (supports_away_) + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_AWAY, + }); traits.set_supports_action(true); return traits; } @@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) { this->target_temperature_low = this->away_config_.default_temperature_low; this->target_temperature_high = this->away_config_.default_temperature_high; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index b0b71cb7d7..49a87027f2 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -27,7 +27,9 @@ template class ControlAction : public Action { call.set_target_temperature(this->target_temperature_.optional_value(x...)); call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); - call.set_away(this->away_.optional_value(x...)); + if (away_.has_value()) { + call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME); + } call.set_fan_mode(this->fan_mode_.optional_value(x...)); call.set_fan_mode(this->custom_fan_mode_.optional_value(x...)); call.set_preset(this->preset_.optional_value(x...)); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index c047d96cdb..5369449839 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -43,9 +43,6 @@ void ClimateCall::perform() { if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } - if (this->away_.has_value()) { - ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_)); - } this->parent_->control(*this); } void ClimateCall::validate_() { @@ -125,12 +122,6 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->away_.has_value()) { - if (!traits.get_supports_away()) { - ESP_LOGW(TAG, " Cannot set away mode for this device!"); - this->away_.reset(); - } - } } ClimateCall &ClimateCall::set_mode(ClimateMode mode) { this->mode_ = mode; @@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { this->set_fan_mode(CLIMATE_FAN_DIFFUSE); } else { - auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes(); - if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) { + if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; this->fan_mode_.reset(); } else { @@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) { } else if (str_equals_case_insensitive(preset, "ACTIVITY")) { this->set_preset(CLIMATE_PRESET_ACTIVITY); } else { - auto custom_presets = this->parent_->get_traits().get_supported_custom_presets(); - if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) { + if (this->parent_->get_traits().supports_custom_preset(preset)) { this->custom_preset_ = preset; this->preset_.reset(); } else { @@ -269,18 +258,23 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -const optional &ClimateCall::get_away() const { return this->away_; } +optional ClimateCall::get_away() const { + if (!this->preset_.has_value()) + return {}; + return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; +} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } ClimateCall &ClimateCall::set_away(bool away) { - this->away_ = away; + this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_away(optional away) { - this->away_ = away; + if (away.has_value()) + this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { @@ -338,20 +332,17 @@ void Climate::save_state_() { } else { state.target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - state.away = this->away; - } if (traits.get_supports_fan_modes() && fan_mode.has_value()) { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { state.uses_custom_fan_mode = true; - auto &custom_fan_modes = traits.get_supported_custom_fan_modes(); - auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value()); - // only set custom fan mode if value exists, otherwise leave it as is - if (it != custom_fan_modes.cend()) { - state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it); + const auto &supported = traits.get_supported_custom_fan_modes(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_fan_mode); + if (it != vec.end()) { + state.custom_fan_mode = std::distance(vec.begin(), it); } } if (traits.get_supports_presets() && preset.has_value()) { @@ -360,11 +351,12 @@ void Climate::save_state_() { } if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { state.uses_custom_preset = true; - auto custom_presets = traits.get_supported_custom_presets(); - auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value()); + const auto &supported = traits.get_supported_custom_presets(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_preset); // only set custom preset if value exists, otherwise leave it as is - if (it != custom_presets.cend()) { - state.custom_preset = std::distance(custom_presets.begin(), it); + if (it != vec.cend()) { + state.custom_preset = std::distance(vec.begin(), it); } } if (traits.get_supports_swing_modes()) { @@ -405,9 +397,6 @@ void Climate::publish_state() { } else { ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); } - if (traits.get_supports_away()) { - ESP_LOGD(TAG, " Away: %s", ONOFF(this->away)); - } // Send state to frontend this->state_callback_.call(); @@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { } else { call.set_target_temperature(this->target_temperature); } - if (traits.get_supports_away()) { - call.set_away(this->away); - } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { call.set_fan_mode(this->fan_mode); } @@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { } else { climate->target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - climate->away = this->away; - } if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) { climate->fan_mode = this->fan_mode; } if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) { - climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &modes = traits.get_supported_custom_fan_modes(); + std::vector modes_vec{modes.begin(), modes.end()}; + if (custom_fan_mode < modes_vec.size()) { + climate->custom_fan_mode = modes_vec[this->custom_fan_mode]; + } } if (traits.get_supports_presets() && !this->uses_custom_preset) { climate->preset = this->preset; } - if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset]; - } if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->preset]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &presets = traits.get_supported_custom_presets(); + std::vector presets_vec{presets.begin(), presets.end()}; + if (custom_preset < presets_vec.size()) { + climate->custom_preset = presets_vec[this->custom_preset]; + } } if (traits.get_supports_swing_modes()) { climate->swing_mode = this->swing_mode; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 3aa29be2fb..4b12c98dc7 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -63,7 +63,9 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(bool away); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); @@ -94,7 +96,8 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - const optional &get_away() const; + ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead") + optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -109,7 +112,6 @@ class ClimateCall { optional target_temperature_; optional target_temperature_low_; optional target_temperature_high_; - optional away_; optional fan_mode_; optional swing_mode_; optional custom_fan_mode_; @@ -120,7 +122,6 @@ class ClimateCall { /// Struct used to save the state of the climate device in restore memory. struct ClimateDeviceRestoreState { ClimateMode mode; - bool away; bool uses_custom_fan_mode{false}; union { ClimateFanMode fan_mode; @@ -191,6 +192,7 @@ class Climate : public Nameable { * Away allows climate devices to have two different target temperature configs: * one for normal mode and one for away mode. */ + ESPDEPRECATED("away is deprecated, use preset instead") bool away{false}; /// The active fan mode of the climate device. diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 4540208a3f..099074a887 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_HOME: + return "HOME"; case climate::CLIMATE_PRESET_ECO: return "ECO"; case climate::CLIMATE_PRESET_AWAY: @@ -92,8 +94,6 @@ const char *climate_preset_to_string(ClimatePreset preset) { return "BOOST"; case climate::CLIMATE_PRESET_COMFORT: return "COMFORT"; - case climate::CLIMATE_PRESET_HOME: - return "HOME"; case climate::CLIMATE_PRESET_SLEEP: return "SLEEP"; case climate::CLIMATE_PRESET_ACTIVITY: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index e129fca91d..7afa2dae55 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -39,7 +39,6 @@ enum ClimateAction : uint8_t { CLIMATE_ACTION_FAN = 6, }; -/// Enum for all modes a climate fan can be in enum ClimateFanMode : uint8_t { /// The fan mode is set to On CLIMATE_FAN_ON = 0, @@ -75,16 +74,16 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 0, + /// Preset is set to HOME + CLIMATE_PRESET_HOME = 0, /// Preset is set to AWAY CLIMATE_PRESET_AWAY = 1, /// Preset is set to BOOST CLIMATE_PRESET_BOOST = 2, /// Preset is set to COMFORT CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 4, + /// Preset is set to ECO + CLIMATE_PRESET_ECO = 4, /// Preset is set to SLEEP CLIMATE_PRESET_SLEEP = 5, /// Preset is set to ACTIVITY diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 774ada785f..c871552360 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -4,55 +4,6 @@ namespace esphome { namespace climate { -bool ClimateTraits::supports_mode(ClimateMode mode) const { - switch (mode) { - case CLIMATE_MODE_OFF: - return true; - case CLIMATE_MODE_HEAT_COOL: - return this->supports_heat_cool_mode_; - case CLIMATE_MODE_AUTO: - return this->supports_auto_mode_; - case CLIMATE_MODE_COOL: - return this->supports_cool_mode_; - case CLIMATE_MODE_HEAT: - return this->supports_heat_mode_; - case CLIMATE_MODE_FAN_ONLY: - return this->supports_fan_only_mode_; - case CLIMATE_MODE_DRY: - return this->supports_dry_mode_; - default: - return false; - } -} -bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; } -void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) { - supports_current_temperature_ = supports_current_temperature; -} -bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } -void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { - supports_two_point_target_temperature_ = supports_two_point_target_temperature; -} -void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } -void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { - supports_heat_cool_mode_ = supports_heat_cool_mode; -} -void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } -void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } -void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { - supports_fan_only_mode_ = supports_fan_only_mode; -} -void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; } -void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } -void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } -float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } -void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) { - visual_min_temperature_ = visual_min_temperature; -} -float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; } -void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) { - visual_max_temperature_ = visual_max_temperature; -} -float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; } int8_t ClimateTraits::get_temperature_accuracy_decimals() const { // use printf %g to find number of digits based on temperature step char buf[32]; @@ -64,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const { return str.length() - dot_pos - 1; } -void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } -bool ClimateTraits::get_supports_away() const { return supports_away_; } -bool ClimateTraits::get_supports_action() const { return supports_action_; } -void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) { - this->supports_fan_mode_on_ = supports_fan_mode_on; -} -void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) { - this->supports_fan_mode_off_ = supports_fan_mode_off; -} -void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) { - this->supports_fan_mode_auto_ = supports_fan_mode_auto; -} -void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) { - this->supports_fan_mode_low_ = supports_fan_mode_low; -} -void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) { - this->supports_fan_mode_medium_ = supports_fan_mode_medium; -} -void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) { - this->supports_fan_mode_high_ = supports_fan_mode_high; -} -void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) { - this->supports_fan_mode_middle_ = supports_fan_mode_middle; -} -void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) { - this->supports_fan_mode_focus_ = supports_fan_mode_focus; -} -void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { - this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; -} -bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const { - switch (fan_mode) { - case climate::CLIMATE_FAN_ON: - return this->supports_fan_mode_on_; - case climate::CLIMATE_FAN_OFF: - return this->supports_fan_mode_off_; - case climate::CLIMATE_FAN_AUTO: - return this->supports_fan_mode_auto_; - case climate::CLIMATE_FAN_LOW: - return this->supports_fan_mode_low_; - case climate::CLIMATE_FAN_MEDIUM: - return this->supports_fan_mode_medium_; - case climate::CLIMATE_FAN_HIGH: - return this->supports_fan_mode_high_; - case climate::CLIMATE_FAN_MIDDLE: - return this->supports_fan_mode_middle_; - case climate::CLIMATE_FAN_FOCUS: - return this->supports_fan_mode_focus_; - case climate::CLIMATE_FAN_DIFFUSE: - return this->supports_fan_mode_diffuse_; - default: - return false; - } -} -bool ClimateTraits::get_supports_fan_modes() const { - return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || - this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || - this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_; -} -void ClimateTraits::set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes) { - this->supported_custom_fan_modes_ = supported_custom_fan_modes; -} -const std::vector ClimateTraits::get_supported_custom_fan_modes() const { - return this->supported_custom_fan_modes_; -} -bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const { - return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(), - custom_fan_mode); -} -bool ClimateTraits::supports_preset(ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - return this->supports_preset_eco_; - case climate::CLIMATE_PRESET_AWAY: - return this->supports_preset_away_; - case climate::CLIMATE_PRESET_BOOST: - return this->supports_preset_boost_; - case climate::CLIMATE_PRESET_COMFORT: - return this->supports_preset_comfort_; - case climate::CLIMATE_PRESET_HOME: - return this->supports_preset_home_; - case climate::CLIMATE_PRESET_SLEEP: - return this->supports_preset_sleep_; - case climate::CLIMATE_PRESET_ACTIVITY: - return this->supports_preset_activity_; - default: - return false; - } -} -void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) { - this->supports_preset_eco_ = supports_preset_eco; -} -void ClimateTraits::set_supports_preset_away(bool supports_preset_away) { - this->supports_preset_away_ = supports_preset_away; -} -void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) { - this->supports_preset_boost_ = supports_preset_boost; -} -void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) { - this->supports_preset_comfort_ = supports_preset_comfort; -} -void ClimateTraits::set_supports_preset_home(bool supports_preset_home) { - this->supports_preset_home_ = supports_preset_home; -} -void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) { - this->supports_preset_sleep_ = supports_preset_sleep; -} -void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) { - this->supports_preset_activity_ = supports_preset_activity; -} -bool ClimateTraits::get_supports_presets() const { - return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ || - this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ || - this->supports_preset_activity_; -} -void ClimateTraits::set_supported_custom_presets(std::vector &supported_custom_presets) { - this->supported_custom_presets_ = supported_custom_presets; -} -const std::vector ClimateTraits::get_supported_custom_presets() const { - return this->supported_custom_presets_; -} -bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const { - return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset); -} -void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) { - this->supports_swing_mode_off_ = supports_swing_mode_off; -} -void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) { - this->supports_swing_mode_both_ = supports_swing_mode_both; -} -void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) { - this->supports_swing_mode_vertical_ = supports_swing_mode_vertical; -} -void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) { - this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal; -} -bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - return this->supports_swing_mode_off_; - case climate::CLIMATE_SWING_BOTH: - return this->supports_swing_mode_both_; - case climate::CLIMATE_SWING_VERTICAL: - return this->supports_swing_mode_vertical_; - case climate::CLIMATE_SWING_HORIZONTAL: - return this->supports_swing_mode_horizontal_; - default: - return false; - } -} -bool ClimateTraits::get_supports_swing_modes() const { - return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ || - supports_swing_mode_horizontal_; -} } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f8e6f87306..b86a0c7774 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "climate_mode.h" +#include namespace esphome { namespace climate { @@ -24,8 +25,6 @@ namespace climate { * - heat mode (increases current temperature) * - dry mode (removes humidity from air) * - fan mode (only turns on fan) - * - supports away - away mode means that the climate device supports two different - * target temperature settings: one target temp setting for "away" mode and one for non-away mode. * - supports action - if the climate device supports reporting the active * current action of the device with the action property. * - supports fan modes - optionally, if it has a fan which can be configured in different ways: @@ -41,95 +40,147 @@ namespace climate { */ class ClimateTraits { public: - bool get_supports_current_temperature() const; - void set_supports_current_temperature(bool supports_current_temperature); - bool get_supports_two_point_target_temperature() const; - void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); - void set_supports_auto_mode(bool supports_auto_mode); - void set_supports_heat_cool_mode(bool supports_heat_cool_mode); - void set_supports_cool_mode(bool supports_cool_mode); - void set_supports_heat_mode(bool supports_heat_mode); - void set_supports_fan_only_mode(bool supports_fan_only_mode); - void set_supports_dry_mode(bool supports_dry_mode); - void set_supports_away(bool supports_away); - bool get_supports_away() const; - void set_supports_action(bool supports_action); - bool get_supports_action() const; - bool supports_mode(ClimateMode mode) const; - void set_supports_fan_mode_on(bool supports_fan_mode_on); - void set_supports_fan_mode_off(bool supports_fan_mode_off); - void set_supports_fan_mode_auto(bool supports_fan_mode_auto); - void set_supports_fan_mode_low(bool supports_fan_mode_low); - void set_supports_fan_mode_medium(bool supports_fan_mode_medium); - void set_supports_fan_mode_high(bool supports_fan_mode_high); - void set_supports_fan_mode_middle(bool supports_fan_mode_middle); - void set_supports_fan_mode_focus(bool supports_fan_mode_focus); - void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); - bool supports_fan_mode(ClimateFanMode fan_mode) const; - bool get_supports_fan_modes() const; - void set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes); - const std::vector get_supported_custom_fan_modes() const; - bool supports_custom_fan_mode(std::string &custom_fan_mode) const; - bool supports_preset(ClimatePreset preset) const; - void set_supports_preset_eco(bool supports_preset_eco); - void set_supports_preset_away(bool supports_preset_away); - void set_supports_preset_boost(bool supports_preset_boost); - void set_supports_preset_comfort(bool supports_preset_comfort); - void set_supports_preset_home(bool supports_preset_home); - void set_supports_preset_sleep(bool supports_preset_sleep); - void set_supports_preset_activity(bool supports_preset_activity); - bool get_supports_presets() const; - void set_supported_custom_presets(std::vector &supported_custom_presets); - const std::vector get_supported_custom_presets() const; - bool supports_custom_preset(std::string &custom_preset) const; - void set_supports_swing_mode_off(bool supports_swing_mode_off); - void set_supports_swing_mode_both(bool supports_swing_mode_both); - void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); - void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); - bool supports_swing_mode(ClimateSwingMode swing_mode) const; - bool get_supports_swing_modes() const; + bool get_supports_current_temperature() const { return supports_current_temperature_; } + void set_supports_current_temperature(bool supports_current_temperature) { + supports_current_temperature_ = supports_current_temperature; + } + bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } + void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { + supports_two_point_target_temperature_ = supports_two_point_target_temperature; + } + void set_supported_modes(std::set modes) { supported_modes_ = std::move(modes); } + void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_fan_only_mode(bool supports_fan_only_mode) { + set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode); + } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } + bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } + const std::set get_supported_modes() const { return supported_modes_; } - float get_visual_min_temperature() const; - void set_visual_min_temperature(float visual_min_temperature); - float get_visual_max_temperature() const; - void set_visual_max_temperature(float visual_max_temperature); - float get_visual_temperature_step() const; + void set_supports_action(bool supports_action) { supports_action_ = supports_action; } + bool get_supports_action() const { return supports_action_; } + + void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } + void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } + bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } + bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); } + const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + + void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { + supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); + } + const std::set &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; } + bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + return supported_custom_fan_modes_.count(custom_fan_mode); + } + + void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } + void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } + bool get_supports_presets() const { return !supported_presets_.empty(); } + const std::set &get_supported_presets() const { return supported_presets_; } + + void set_supported_custom_presets(std::set supported_custom_presets) { + supported_custom_presets_ = std::move(supported_custom_presets); + } + const std::set &get_supported_custom_presets() const { return supported_custom_presets_; } + bool supports_custom_preset(const std::string &custom_preset) const { + return supported_custom_presets_.count(custom_preset); + } + ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead") + void set_supports_away(bool supports) { + if (supports) { + supported_presets_.insert(CLIMATE_PRESET_AWAY); + supported_presets_.insert(CLIMATE_PRESET_HOME); + } + } + ESPDEPRECATED("This method is deprecated, use supports_preset() instead") + bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } + + void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } + void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_horizontal(bool supported) { + set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); + } + bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } + bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } + const std::set get_supported_swing_modes() { return supported_swing_modes_; } + + float get_visual_min_temperature() const { return visual_min_temperature_; } + void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } + float get_visual_max_temperature() const { return visual_max_temperature_; } + void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } + float get_visual_temperature_step() const { return visual_temperature_step_; } int8_t get_temperature_accuracy_decimals() const; - void set_visual_temperature_step(float temperature_step); + void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } protected: + void set_mode_support_(climate::ClimateMode mode, bool supported) { + if (supported) { + supported_modes_.insert(mode); + } else { + supported_modes_.erase(mode); + } + } + void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) { + if (supported) { + supported_fan_modes_.insert(mode); + } else { + supported_fan_modes_.erase(mode); + } + } + void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) { + if (supported) { + supported_swing_modes_.insert(mode); + } else { + supported_swing_modes_.erase(mode); + } + } + bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; - bool supports_auto_mode_{false}; - bool supports_heat_cool_mode_{false}; - bool supports_cool_mode_{false}; - bool supports_heat_mode_{false}; - bool supports_fan_only_mode_{false}; - bool supports_dry_mode_{false}; - bool supports_away_{false}; + std::set supported_modes_ = {climate::CLIMATE_MODE_OFF}; bool supports_action_{false}; - bool supports_fan_mode_on_{false}; - bool supports_fan_mode_off_{false}; - bool supports_fan_mode_auto_{false}; - bool supports_fan_mode_low_{false}; - bool supports_fan_mode_medium_{false}; - bool supports_fan_mode_high_{false}; - bool supports_fan_mode_middle_{false}; - bool supports_fan_mode_focus_{false}; - bool supports_fan_mode_diffuse_{false}; - bool supports_swing_mode_off_{false}; - bool supports_swing_mode_both_{false}; - bool supports_swing_mode_vertical_{false}; - bool supports_swing_mode_horizontal_{false}; - bool supports_preset_eco_{false}; - bool supports_preset_away_{false}; - bool supports_preset_boost_{false}; - bool supports_preset_comfort_{false}; - bool supports_preset_home_{false}; - bool supports_preset_sleep_{false}; - bool supports_preset_activity_{false}; - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + std::set supported_fan_modes_; + std::set supported_swing_modes_; + std::set supported_presets_; + std::set supported_custom_fan_modes_; + std::set supported_custom_presets_; float visual_min_temperature_{10}; float visual_max_temperature_{30}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index f88b2174ee..16a8e78414 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,63 +9,22 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_); traits.set_visual_temperature_step(this->temperature_step_); - for (auto fan_mode : this->fan_modes_) { - switch (fan_mode) { - case climate::CLIMATE_FAN_AUTO: - traits.set_supports_fan_mode_auto(true); - break; - case climate::CLIMATE_FAN_DIFFUSE: - traits.set_supports_fan_mode_diffuse(true); - break; - case climate::CLIMATE_FAN_FOCUS: - traits.set_supports_fan_mode_focus(true); - break; - case climate::CLIMATE_FAN_HIGH: - traits.set_supports_fan_mode_high(true); - break; - case climate::CLIMATE_FAN_LOW: - traits.set_supports_fan_mode_low(true); - break; - case climate::CLIMATE_FAN_MEDIUM: - traits.set_supports_fan_mode_medium(true); - break; - case climate::CLIMATE_FAN_MIDDLE: - traits.set_supports_fan_mode_middle(true); - break; - case climate::CLIMATE_FAN_OFF: - traits.set_supports_fan_mode_off(true); - break; - case climate::CLIMATE_FAN_ON: - traits.set_supports_fan_mode_on(true); - break; - } - } - for (auto swing_mode : this->swing_modes_) { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - traits.set_supports_swing_mode_off(true); - break; - case climate::CLIMATE_SWING_BOTH: - traits.set_supports_swing_mode_both(true); - break; - case climate::CLIMATE_SWING_VERTICAL: - traits.set_supports_swing_mode_vertical(true); - break; - case climate::CLIMATE_SWING_HORIZONTAL: - traits.set_supports_swing_mode_horizontal(true); - break; - } - } + traits.set_supported_fan_modes(fan_modes_); + traits.set_supported_swing_modes(swing_modes_); return traits; } diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 7a69b19786..ff04fa4e2f 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -19,9 +19,8 @@ namespace climate_ir { class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { public: ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, - bool supports_dry = false, bool supports_fan_only = false, - std::vector fan_modes = {}, - std::vector swing_modes = {}) { + bool supports_dry = false, bool supports_fan_only = false, std::set fan_modes = {}, + std::set swing_modes = {}) { this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; @@ -58,8 +57,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: bool supports_heat_{true}; bool supports_dry_{false}; bool supports_fan_only_{false}; - std::vector fan_modes_ = {}; - std::vector swing_modes_ = {}; + std::set fan_modes_ = {}; + std::set swing_modes_ = {}; remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; diff --git a/esphome/components/daikin/daikin.h b/esphome/components/daikin/daikin.h index c0a472bce7..b4ac309de9 100644 --- a/esphome/components/daikin/daikin.h +++ b/esphome/components/daikin/daikin.h @@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19; class DaikinClimate : public climate_ir::ClimateIR { public: DaikinClimate() - : climate_ir::ClimateIR( - DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} protected: // Transmit via IR the state of this climate controller. diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.h b/esphome/components/hitachi_ac344/hitachi_ac344.h index 9e850d9b53..1d0719f11b 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.h +++ b/esphome/components/hitachi_ac344/hitachi_ac344.h @@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8; class HitachiClimate : public climate_ir::ClimateIR { public: HitachiClimate() - : climate_ir::ClimateIR( - HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + : climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} protected: uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00, diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 481a6da54d..b3b196aad2 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(true); - traits.set_supports_dry_mode(true); - traits.set_supports_heat_mode(true); - traits.set_supports_fan_only_mode(true); - traits.set_supports_fan_mode_auto(true); - traits.set_supports_fan_mode_low(true); - traits.set_supports_fan_mode_medium(true); - traits.set_supports_fan_mode_high(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_FAN_ONLY, + }); + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + }); traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supports_swing_mode_off(true); - traits.set_supports_swing_mode_vertical(true); - traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_); - traits.set_supports_swing_mode_both(this->traits_swing_both_); - traits.set_supports_preset_home(true); - traits.set_supports_preset_eco(this->traits_preset_eco_); - traits.set_supports_preset_sleep(this->traits_preset_sleep_); - traits.set_supports_preset_boost(this->traits_preset_boost_); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL, + }); + if (traits_swing_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (traits_swing_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + }); + if (traits_preset_eco_) + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + if (traits_preset_sleep_) + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + if (traits_preset_boost_) + traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); traits.set_supported_custom_presets(this->traits_custom_presets_); traits.set_supports_current_temperature(true); return traits; diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h index 0a63312961..d5f29529df 100644 --- a/esphome/components/midea_ac/midea_climate.h +++ b/esphome/components/midea_ac/midea_climate.h @@ -1,9 +1,9 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/midea_dongle/midea_dongle.h" #include "esphome/components/climate/climate.h" +#include "esphome/components/midea_dongle/midea_dongle.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" #include "midea_frame.h" namespace esphome { @@ -26,10 +26,12 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::vector custom_fan_modes) { - this->traits_custom_fan_modes_ = custom_fan_modes; + void set_custom_fan_modes(std::set custom_fan_modes) { + this->traits_custom_fan_modes_ = std::move(custom_fan_modes); + } + void set_custom_presets(std::set custom_presets) { + this->traits_custom_presets_ = std::move(custom_presets); } - void set_custom_presets(std::vector custom_presets) { this->traits_custom_presets_ = custom_presets; } bool allow_custom_preset(const std::string &custom_preset) const; protected: @@ -53,8 +55,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu bool traits_preset_eco_{false}; bool traits_preset_sleep_{false}; bool traits_preset_boost_{false}; - std::vector traits_custom_fan_modes_{{}}; - std::vector traits_custom_presets_{{}}; + std::set traits_custom_fan_modes_{{}}; + std::set traits_custom_presets_{{}}; }; } // namespace midea_ac diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47923dc924..ed3193ae97 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // temp_step root["temp_step"] = traits.get_visual_temperature_step(); - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic root["away_mode_cmd_t"] = this->get_away_command_topic(); // away_mode_state_topic @@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() { }); } - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { auto onoff = parse_on_off(payload.c_str()); auto call = this->device_->make_call(); switch (onoff) { case PARSE_ON: - call.set_away(true); + call.set_preset(CLIMATE_PRESET_AWAY); break; case PARSE_OFF: - call.set_away(false); + call.set_preset(CLIMATE_PRESET_HOME); break; case PARSE_TOGGLE: - call.set_away(!this->device_->away); + call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); break; case PARSE_NONE: default: @@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.get_supports_away()) { - std::string payload = ONOFF(this->device_->away); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { + std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); if (!this->publish(this->get_away_state_topic(), payload)) success = false; } diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 0423ab27fc..b4660feb32 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); - traits.set_supports_cool_mode(this->supports_cool_()); - traits.set_supports_heat_mode(this->supports_heat_()); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_()) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_()) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_action(true); return traits; } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a96c702473..65dd398197 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) { + if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_away_(*call.get_away()); + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); } else { - this->away = *call.get_away(); + this->preset = *call.get_preset(); + ; } } // set point validation @@ -78,27 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(this->supports_auto_); - traits.set_supports_heat_cool_mode(this->supports_heat_cool_); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_fan_mode_on(this->supports_fan_mode_on_); - traits.set_supports_fan_mode_off(this->supports_fan_mode_off_); - traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_); - traits.set_supports_fan_mode_low(this->supports_fan_mode_low_); - traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_); - traits.set_supports_fan_mode_high(this->supports_fan_mode_high_); - traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_); - traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_); - traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_); - traits.set_supports_swing_mode_both(this->supports_swing_mode_both_); - traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_); - traits.set_supports_swing_mode_off(this->supports_swing_mode_off_); - traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_); + if (supports_auto_) + traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); + if (supports_heat_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (supports_fan_mode_on_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON); + if (supports_fan_mode_off_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF); + if (supports_fan_mode_auto_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + if (supports_fan_mode_low_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (supports_fan_mode_medium_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (supports_fan_mode_high_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (supports_fan_mode_middle_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (supports_fan_mode_focus_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS); + if (supports_fan_mode_diffuse_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE); + + if (supports_swing_mode_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + if (supports_swing_mode_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (supports_swing_mode_off_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); + if (supports_swing_mode_vertical_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + if (supports_away_) + traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY}); + traits.set_supports_two_point_target_temperature(this->supports_two_points_); - traits.set_supports_away(this->supports_away_); traits.set_supports_action(true); return traits; } @@ -399,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) { } else this->target_temperature = this->away_config_.default_temperature; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 9c9cf9f3e7..fbd25ee03a 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_cool_mode(this->supports_cool_); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); traits.set_supports_action(true); return traits; } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e53e5fcccc..1505f4ead2 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(YASHIMA_TEMP_MIN); traits.set_visual_max_temperature(YASHIMA_TEMP_MAX); traits.set_visual_temperature_step(1); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py old mode 100644 new mode 100755 index 97cc95e556..05ad94a69d --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Python 3 script to automatically generate C++ classes for ESPHome's native API. It's pretty crappy spaghetti code, but it works. From e3f36c033ec486fe8e1182c62dc1f28adb8d6dcf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 22:02:18 +0200 Subject: [PATCH 036/285] API raise minor version for climate changes (#1947) --- esphome/components/api/api_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 68187f259e..a04dc0630b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -615,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 4; + resp.api_version_minor = 5; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; From 6009c7edb40a6ee2315687c056dd6170d3d877bc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 22 Jun 2021 10:53:10 +0200 Subject: [PATCH 037/285] Disallow power_save_mode NONE if used together with BLE (#1950) --- esphome/components/http_request/__init__.py | 19 ++++------- esphome/components/wifi/__init__.py | 36 ++++++++++++++++++++- esphome/final_validate.py | 24 ++++++++++++++ tests/test1.yaml | 2 +- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4475060df..7dffdae27f 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -7,10 +7,7 @@ from esphome import automation from esphome.const import ( CONF_ID, CONF_TIMEOUT, - CONF_ESPHOME, CONF_METHOD, - CONF_ARDUINO_VERSION, - ARDUINO_VERSION_ESP8266, CONF_TRIGGER_ID, CONF_URL, ) @@ -78,23 +75,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_framework(config): - if CORE.is_esp32: +def validate_framework(value): + if not CORE.is_esp8266: + # only for ESP8266 return - # only for ESP8266 - path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] - version: str = fv.full_config.get().get_config_for_path(path) - - reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} - framework_version = reverse_map.get(version) + framework_version = fv.get_arduino_framework_version() if framework_version is None or framework_version == "dev": return if framework_version < "2.5.1": raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1", - path=[cv.ROOT_CONFIG_PATH] + path, + "This component is not supported on arduino framework version below 2.5.1, ", + "please check esphome->arduino_version", ) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index fa28eaffd4..5a81d6a8f5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -148,7 +148,41 @@ def final_validate(config): ) -FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) +def final_validate_power_esp32_ble(value): + if not CORE.is_esp32: + return + if value != "NONE": + # WiFi should be in modem sleep (!=NONE) with BLE coexistence + # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep + return + framework_version = fv.get_arduino_framework_version() + if framework_version not in (None, "dev") and framework_version < "1.0.5": + # Only frameworks 1.0.5+ impacted + return + full = fv.full_config.get() + for conflicting in [ + "esp32_ble", + "esp32_ble_beacon", + "esp32_ble_server", + "esp32_ble_tracker", + ]: + if conflicting in full: + raise cv.Invalid( + f"power_save_mode NONE is incompatible with {conflicting}. " + f"Please remove the power save mode. See also " + f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582" + ) + + +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble, + }, + extra=cv.ALLOW_EXTRA, + ), + final_validate, +) def _validate(config): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 50fdbaf3f4..47071b5391 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,6 +4,13 @@ import contextvars from esphome.types import ConfigFragmentType, ID, ConfigPathType import esphome.config_validation as cv +from esphome.const import ( + ARDUINO_VERSION_ESP32, + ARDUINO_VERSION_ESP8266, + CONF_ESPHOME, + CONF_ARDUINO_VERSION, +) +from esphome.core import CORE class FinalValidateConfig(ABC): @@ -55,3 +62,20 @@ def id_declaration_match_schema(schema): return schema(declaration_config) return validator + + +def get_arduino_framework_version(): + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + # This is run after core validation, so the property is set even if user didn't + version: str = full_config.get().get_config_for_path(path) + + if CORE.is_esp32: + version_map = ARDUINO_VERSION_ESP32 + elif CORE.is_esp8266: + version_map = ARDUINO_VERSION_ESP8266 + else: + raise ValueError("Platform not supported yet for this validator") + + reverse_map = {v: k for k, v in version_map.items()} + framework_version = reverse_map.get(version) + return framework_version diff --git a/tests/test1.yaml b/tests/test1.yaml index 29df5857d3..08e1d63534 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -80,7 +80,7 @@ wifi: dns2: 1.2.2.1 domain: .local reboot_timeout: 120s - power_save_mode: none + power_save_mode: light http_request: useragent: esphome/device From 1d8c170f48a59a2604a7c8c022603162a7f22fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:25:19 +0200 Subject: [PATCH 038/285] Add climate preset NONE again (#1951) --- esphome/components/api/api.proto | 15 ++++++----- esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 15 ++++++----- esphome/components/climate/climate_mode.cpp | 2 ++ esphome/components/climate/climate_mode.h | 30 +++++++++++---------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 87a7cf4749..a5bd9aec6d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,13 +710,14 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_HOME = 0; - CLIMATE_PRESET_AWAY = 1; - CLIMATE_PRESET_BOOST = 2; - CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_ECO = 4; - CLIMATE_PRESET_SLEEP = 5; - CLIMATE_PRESET_ACTIVITY = 6; + CLIMATE_PRESET_NONE = 0; + CLIMATE_PRESET_HOME = 1; + CLIMATE_PRESET_AWAY = 2; + CLIMATE_PRESET_BOOST = 3; + CLIMATE_PRESET_COMFORT = 4; + CLIMATE_PRESET_ECO = 5; + CLIMATE_PRESET_SLEEP = 6; + CLIMATE_PRESET_ACTIVITY = 7; } message ListEntitiesClimateResponse { option (id) = 46; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1e023f3988..e53ac2019a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,6 +192,8 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { + case enums::CLIMATE_PRESET_NONE: + return "CLIMATE_PRESET_NONE"; case enums::CLIMATE_PRESET_HOME: return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7f37c0b94b..956cecdeb9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,13 +90,14 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_HOME = 0, - CLIMATE_PRESET_AWAY = 1, - CLIMATE_PRESET_BOOST = 2, - CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_ECO = 4, - CLIMATE_PRESET_SLEEP = 5, - CLIMATE_PRESET_ACTIVITY = 6, + CLIMATE_PRESET_NONE = 0, + CLIMATE_PRESET_HOME = 1, + CLIMATE_PRESET_AWAY = 2, + CLIMATE_PRESET_BOOST = 3, + CLIMATE_PRESET_COMFORT = 4, + CLIMATE_PRESET_ECO = 5, + CLIMATE_PRESET_SLEEP = 6, + CLIMATE_PRESET_ACTIVITY = 7, }; } // namespace enums diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 099074a887..7a626942eb 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_NONE: + return "NONE"; case climate::CLIMATE_PRESET_HOME: return "HOME"; case climate::CLIMATE_PRESET_ECO: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 7afa2dae55..07fbf32b26 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -74,20 +74,22 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 0, - /// Preset is set to AWAY - CLIMATE_PRESET_AWAY = 1, - /// Preset is set to BOOST - CLIMATE_PRESET_BOOST = 2, - /// Preset is set to COMFORT - CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 4, - /// Preset is set to SLEEP - CLIMATE_PRESET_SLEEP = 5, - /// Preset is set to ACTIVITY - CLIMATE_PRESET_ACTIVITY = 6, + /// No preset is active + CLIMATE_PRESET_NONE = 0, + /// Device is in home preset + CLIMATE_PRESET_HOME = 1, + /// Device is in away preset + CLIMATE_PRESET_AWAY = 2, + /// Device is in boost preset + CLIMATE_PRESET_BOOST = 3, + /// Device is in comfort preset + CLIMATE_PRESET_COMFORT = 4, + /// Device is running an energy-saving preset + CLIMATE_PRESET_ECO = 5, + /// Device is prepared for sleep + CLIMATE_PRESET_SLEEP = 6, + /// Device is reacting to activity (e.g., movement sensors) + CLIMATE_PRESET_ACTIVITY = 7, }; /// Convert the given ClimateMode to a human-readable string. From 2bf70d7d003ebd857f2ca57d583c5b2ad86099af Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:27:08 +0200 Subject: [PATCH 039/285] Compat argv parsing improvements (#1952) --- esphome/__main__.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 48f8bea083..232652db9f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -514,14 +514,26 @@ def parse_args(argv): compat_parser.error = _raise - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - argv = argv[0:last_option] + [result.command] + result.configuration + unparsed - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - deprecated_argv_suggestion = None + deprecated_argv_suggestion = None + + if ["dashboard", "config"] == argv[1:3]: + # this is most likely meant in new-style arg format. do not try compat parsing + pass + else: + try: + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + argv = ( + argv[0:last_option] + [result.command] + result.configuration + unparsed + ) + deprecated_argv_suggestion = argv + except argparse.ArgumentError: + # This is not an old-style command line, so we don't have to do anything. + pass # And continue on with regular parsing parser = argparse.ArgumentParser( From 96721f305f7e8c0e514ec993d35b70869249b33a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:38:59 +1200 Subject: [PATCH 040/285] Bump dashboard to 20210623.0 (#1958) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12500d57b7..cc9059f0d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210622.0 +esphome-dashboard==20210623.0 From 8600620305b12319a8b1c1ff74ef9faa60b087cc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:49:45 +1200 Subject: [PATCH 041/285] Bump version to v1.19.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index de33ebc2de..02d0491cfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 18c08f24adf5f2f54db8233ede436b9e1ef3c6b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 07:45:05 +1200 Subject: [PATCH 042/285] Bump version to v1.20.0b1 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 094dcace42..30c4748933 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,8 +1,8 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 19 -PATCH_VERSION = "0b7" +MINOR_VERSION = 20 +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 4dbf1c521ecb6a70dec86401f998686be83bcd52 Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Wed, 14 Jul 2021 20:51:15 -0400 Subject: [PATCH 043/285] Nextion upload and sensors (#1464) Co-authored-by: Senex Crenshaw --- CODEOWNERS | 5 + esphome/components/nextion/__init__.py | 5 + esphome/components/nextion/automation.h | 30 + esphome/components/nextion/base_component.py | 126 ++ esphome/components/nextion/binary_sensor.py | 34 - .../nextion/binary_sensor/__init__.py | 54 + .../binary_sensor/nextion_binarysensor.cpp | 69 + .../binary_sensor/nextion_binarysensor.h | 42 + esphome/components/nextion/display.py | 75 +- esphome/components/nextion/nextion.cpp | 1143 ++++++++++++++--- esphome/components/nextion/nextion.h | 527 +++++++- esphome/components/nextion/nextion_base.h | 58 + .../components/nextion/nextion_commands.cpp | 234 ++++ .../components/nextion/nextion_component.cpp | 116 ++ .../components/nextion/nextion_component.h | 49 + .../nextion/nextion_component_base.h | 95 ++ esphome/components/nextion/nextion_upload.cpp | 343 +++++ esphome/components/nextion/sensor/__init__.py | 99 ++ .../nextion/sensor/nextion_sensor.cpp | 110 ++ .../nextion/sensor/nextion_sensor.h | 49 + esphome/components/nextion/switch/__init__.py | 39 + .../nextion/switch/nextion_switch.cpp | 52 + .../nextion/switch/nextion_switch.h | 34 + .../nextion/text_sensor/__init__.py | 38 + .../text_sensor/nextion_textsensor.cpp | 49 + .../nextion/text_sensor/nextion_textsensor.h | 32 + esphome/components/uart/uart.h | 1 + script/ci-custom.py | 3 +- tests/test1.yaml | 9 - tests/test3.yaml | 38 +- 30 files changed, 3295 insertions(+), 263 deletions(-) create mode 100644 esphome/components/nextion/automation.h create mode 100644 esphome/components/nextion/base_component.py delete mode 100644 esphome/components/nextion/binary_sensor.py create mode 100644 esphome/components/nextion/binary_sensor/__init__.py create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.h create mode 100644 esphome/components/nextion/nextion_base.h create mode 100644 esphome/components/nextion/nextion_commands.cpp create mode 100644 esphome/components/nextion/nextion_component.cpp create mode 100644 esphome/components/nextion/nextion_component.h create mode 100644 esphome/components/nextion/nextion_component_base.h create mode 100644 esphome/components/nextion/nextion_upload.cpp create mode 100644 esphome/components/nextion/sensor/__init__.py create mode 100644 esphome/components/nextion/sensor/nextion_sensor.cpp create mode 100644 esphome/components/nextion/sensor/nextion_sensor.h create mode 100644 esphome/components/nextion/switch/__init__.py create mode 100644 esphome/components/nextion/switch/nextion_switch.cpp create mode 100644 esphome/components/nextion/switch/nextion_switch.h create mode 100644 esphome/components/nextion/text_sensor/__init__.py create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.cpp create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index d6769800cd..557fe7cc08 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov esphome/components/midea_dongle/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core +esphome/components/nextion/* @senexcrenshaw +esphome/components/nextion/binary_sensor/* @senexcrenshaw +esphome/components/nextion/sensor/* @senexcrenshaw +esphome/components/nextion/switch/* @senexcrenshaw +esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 67a49df9fa..924d58198d 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,3 +1,8 @@ import esphome.codegen as cg +from esphome.components import uart nextion_ns = cg.esphome_ns.namespace("nextion") +Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) +nextion_ref = Nextion.operator("ref") + +CONF_NEXTION_ID = "nextion_id" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h new file mode 100644 index 0000000000..5f4219acb1 --- /dev/null +++ b/esphome/components/nextion/automation.h @@ -0,0 +1,30 @@ +#pragma once +#include "esphome/core/automation.h" +#include "nextion.h" + +namespace esphome { +namespace nextion { + +class SetupTrigger : public Trigger<> { + public: + explicit SetupTrigger(Nextion *nextion) { + nextion->add_setup_state_callback([this]() { this->trigger(); }); + } +}; + +class SleepTrigger : public Trigger<> { + public: + explicit SleepTrigger(Nextion *nextion) { + nextion->add_sleep_state_callback([this]() { this->trigger(); }); + } +}; + +class WakeTrigger : public Trigger<> { + public: + explicit WakeTrigger(Nextion *nextion) { + nextion->add_wake_state_callback([this]() { this->trigger(); }); + } +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py new file mode 100644 index 0000000000..3bf828aafa --- /dev/null +++ b/esphome/components/nextion/base_component.py @@ -0,0 +1,126 @@ +from string import ascii_letters, digits +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import color + +from . import CONF_NEXTION_ID +from . import Nextion + +CONF_VARIABLE_NAME = "variable_name" +CONF_COMPONENT_NAME = "component_name" +CONF_WAVE_CHANNEL_ID = "wave_channel_id" +CONF_WAVE_MAX_VALUE = "wave_max_value" +CONF_PRECISION = "precision" +CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" +CONF_TFT_URL = "tft_url" +CONF_ON_SLEEP = "on_sleep" +CONF_ON_WAKE = "on_wake" +CONF_ON_SETUP = "on_setup" +CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" +CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" +CONF_WAVE_MAX_LENGTH = "wave_max_length" +CONF_BACKGROUND_COLOR = "background_color" +CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" +CONF_FOREGROUND_COLOR = "foreground_color" +CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_FONT_ID = "font_id" +CONF_VISIBLE = "visible" + + +def NextionName(value): + valid_chars = ascii_letters + digits + "." + if not isinstance(value, str) or len(value) > 29: + raise cv.Invalid("Must be a string less than 29 characters") + + for char in value: + if char not in valid_chars: + raise cv.Invalid( + "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( + char + ) + ) + + return value + + +CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_VISIBLE, default=True): cv.boolean, + } +) + + +CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + +CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_VARIABLE_NAME): NextionName, + } + ) +) + +CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + + +CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color), + cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color), + } + ) +) + + +async def setup_component_core_(var, config, arg): + + if CONF_VARIABLE_NAME in config: + cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME])) + elif CONF_COMPONENT_NAME in config: + cg.add( + var.set_variable_name( + config[CONF_COMPONENT_NAME], + config[CONF_COMPONENT_NAME] + arg, + ) + ) + + if CONF_BACKGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR]) + cg.add(var.set_background_color(color_component)) + + if CONF_BACKGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR]) + cg.add(var.set_background_pressed_color(color_component)) + + if CONF_FOREGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR]) + cg.add(var.set_foreground_color(color_component)) + + if CONF_FOREGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR]) + cg.add(var.set_foreground_pressed_color(color_component)) + + if CONF_FONT_ID in config: + cg.add(var.set_font_id(config[CONF_FONT_ID])) + + if CONF_VISIBLE in config: + cg.add(var.set_visible(config[CONF_VISIBLE])) diff --git a/esphome/components/nextion/binary_sensor.py b/esphome/components/nextion/binary_sensor.py deleted file mode 100644 index ed4e8d832a..0000000000 --- a/esphome/components/nextion/binary_sensor.py +++ /dev/null @@ -1,34 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from . import nextion_ns -from .display import Nextion - -DEPENDENCIES = ["display"] - -CONF_NEXTION_ID = "nextion_id" - -NextionTouchComponent = nextion_ns.class_( - "NextionTouchComponent", binary_sensor.BinarySensor -) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTouchComponent), - cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), - cv.Required(CONF_PAGE_ID): cv.uint8_t, - cv.Required(CONF_COMPONENT_ID): cv.uint8_t, - } -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) - - hub = await cg.get_variable(config[CONF_NEXTION_ID]) - cg.add(hub.register_touch_component(var)) - - cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) - cg.add(var.set_page_id(config[CONF_PAGE_ID])) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py new file mode 100644 index 0000000000..090fae3429 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + + +from ..base_component import ( + setup_component_core_, + CONFIG_BINARY_SENSOR_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionBinarySensor = nextion_ns.class_( + "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionBinarySensor), + cv.Optional(CONF_PAGE_ID): cv.uint8_t, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + } + ) + .extend(CONFIG_BINARY_SENSOR_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_at_least_one_key( + CONF_PAGE_ID, + CONF_COMPONENT_ID, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + ), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await binary_sensor.register_binary_sensor(var, config) + await cg.register_component(var, config) + + if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}: + cg.add(hub.register_touch_component(var)) + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + cg.add(var.set_page_id(config[CONF_PAGE_ID])) + + if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: + await setup_component_core_(var, config, ".val") + cg.add(hub.register_binarysensor_component(var)) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp new file mode 100644 index 0000000000..bf6e74cb38 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -0,0 +1,69 @@ +#include "nextion_binarysensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_binarysensor"; + +void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + if (this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) { + if (this->page_id_ == page_id && this->component_id_ == component_id) { + this->publish_state(state); + } +} + +void NextionBinarySensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + this->nextion_->add_to_get_queue(this); +} + +void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (this->component_id_ == 0) // This is a legacy touch component + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(), + ONOFF(this->variable_name_.c_str())); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h new file mode 100644 index 0000000000..b6b23ada85 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -0,0 +1,42 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionBinarySensor; + +class NextionBinarySensor : public NextionComponent, + public binary_sensor::BinarySensorInitiallyOff, + public PollingComponent { + public: + NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } + + void update_component() override { this->update(); } + void update() override; + void send_state_to_nextion() override { this->set_state(this->state, false); }; + void process_bool(const std::string &variable_name, bool state) override; + void process_touch(uint8_t page_id, uint8_t component_id, bool state) override; + + // Set the components page id for Nextion Touch Component + void set_page_id(uint8_t page_id) { page_id_ = page_id; } + // Set the components component id for Nextion Touch Component + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + uint8_t page_id_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 7d7018a4c4..e693b2f1ec 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -1,20 +1,58 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import display, uart -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS -from . import nextion_ns +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_TRIGGER_ID, +) + +from . import Nextion, nextion_ns, nextion_ref +from .base_component import ( + CONF_ON_SLEEP, + CONF_ON_WAKE, + CONF_ON_SETUP, + CONF_TFT_URL, + CONF_TOUCH_SLEEP_TIMEOUT, + CONF_WAKE_UP_PAGE, + CONF_AUTO_WAKE_ON_TOUCH, +) + +CODEOWNERS = ["@senexcrenshaw"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["binary_sensor"] +AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"] -Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) -NextionRef = Nextion.operator("ref") +SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template()) +SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) +WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), + cv.Optional(CONF_TFT_URL): cv.string, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_ON_SETUP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger), + } + ), + cv.Optional(CONF_ON_SLEEP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger), + } + ), + cv.Optional(CONF_ON_WAKE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger), + } + ), + cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), + cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int, + cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -31,8 +69,33 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + if CONF_TFT_URL in config: + cg.add_define("USE_TFT_UPLOAD") + cg.add(var.set_tft_url(config[CONF_TFT_URL])) + + if CONF_TOUCH_SLEEP_TIMEOUT in config: + cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) + + if CONF_WAKE_UP_PAGE in config: + cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) + + if CONF_AUTO_WAKE_ON_TOUCH in config: + cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + await display.register_display(var, config) + + for conf in config.get(CONF_ON_SETUP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_SLEEP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_WAKE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fe0767342b..9a5424917f 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1,5 +1,7 @@ #include "nextion.h" +#include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace nextion { @@ -7,69 +9,171 @@ namespace nextion { static const char *const TAG = "nextion"; void Nextion::setup() { - this->send_command_no_ack(""); - this->send_command_printf("bkcmd=3"); - this->set_backlight_brightness(static_cast(brightness_ * 100)); - this->goto_page("0"); + this->is_setup_ = false; + this->ignore_is_setup_ = true; + + // Wake up the nextion + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + // Reboot it + this->send_command_("rest"); + + this->ignore_is_setup_ = false; } -float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; } + +bool Nextion::send_command_(const std::string &command) { + if (!this->ignore_is_setup_ && !this->is_setup()) { + return false; + } + + ESP_LOGN(TAG, "send_command %s", command.c_str()); + + this->write_str(command.c_str()); + const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; + this->write_array(to_send, sizeof(to_send)); + return true; +} + +bool Nextion::check_connect_() { + if (this->get_is_connected_()) + return true; + + if (this->comok_sent_ == 0) { + this->reset_(false); + + this->ignore_is_setup_ = true; + this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating + this->send_command_("connect"); + + this->comok_sent_ = millis(); + this->ignore_is_setup_ = false; + + return false; + } + + if (millis() - this->comok_sent_ <= 500) // Wait 500 ms + return false; + + std::string response; + + this->recv_ret_string_(response, 0, false); + if (response.empty() || response.find("comok") == std::string::npos) { +#ifdef NEXTION_PROTOCOL_LOG + ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); + for (int i = 0; i < response.length(); i++) { + ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); + } +#endif + + ESP_LOGW(TAG, "Nextion is not connected! "); + comok_sent_ = 0; + return false; + } + + this->ignore_is_setup_ = true; + ESP_LOGI(TAG, "Nextion is connected"); + this->is_connected_ = true; + + ESP_LOGN(TAG, "connect request %s", response.c_str()); + + size_t start; + size_t end = 0; + std::vector connect_info; + while ((start = response.find_first_not_of(',', end)) != std::string::npos) { + end = response.find(',', start); + connect_info.push_back(response.substr(start, end - start)); + } + + if (connect_info.size() == 7) { + ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size()); + + this->device_model_ = connect_info[2]; + this->firmware_version_ = connect_info[3]; + this->serial_number_ = connect_info[5]; + this->flash_size_ = connect_info[6]; + } else { + ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str()); + } + + this->ignore_is_setup_ = false; + this->dump_config(); + return true; +} + +void Nextion::reset_(bool reset_nextion) { + uint8_t d; + + while (this->available()) { // Clear receive buffer + this->read_byte(&d); + }; + this->nextion_queue_.clear(); +} + +void Nextion::dump_config() { + ESP_LOGCONFIG(TAG, "Nextion:"); + ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str()); + ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str()); + ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str()); + ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False"); + + if (this->touch_sleep_timeout_ != 0) { + ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_); + } +} + +float Nextion::get_setup_priority() const { return setup_priority::DATA; } void Nextion::update() { + if (!this->is_setup()) { + return; + } if (this->writer_.has_value()) { (*this->writer_)(*this); } } -void Nextion::send_command_no_ack(const char *command) { - // Flush RX... - this->loop(); - this->write_str(command); - const uint8_t data[3] = {0xFF, 0xFF, 0xFF}; - this->write_array(data, sizeof(data)); +void Nextion::add_sleep_state_callback(std::function &&callback) { + this->sleep_callback_.add(std::move(callback)); } -bool Nextion::ack_() { - if (!this->wait_for_ack_) - return true; +void Nextion::add_wake_state_callback(std::function &&callback) { + this->wake_callback_.add(std::move(callback)); +} - uint32_t start = millis(); - while (!this->read_until_ack_()) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Waiting for ACK timed out!"); - return false; - } +void Nextion::add_setup_state_callback(std::function &&callback) { + this->setup_callback_.add(std::move(callback)); +} + +void Nextion::update_all_components() { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->update_component(); + } + for (auto *sensortype : this->sensortype_) { + sensortype->update_component(); + } + for (auto *switchtype : this->switchtype_) { + switchtype->update_component(); + } + for (auto *textsensortype : this->textsensortype_) { + textsensortype->update_component(); } - return true; } -void Nextion::set_component_text(const char *component, const char *text) { - this->send_command_printf("%s.txt=\"%s\"", component, text); -} -void Nextion::set_component_value(const char *component, int value) { - this->send_command_printf("%s.val=%d", component, value); -} -void Nextion::display_picture(int picture_id, int x_start, int y_start) { - this->send_command_printf("pic %d %d %d", x_start, y_start, picture_id); -} -void Nextion::set_component_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco2=\"%s\"", component, color); -} -void Nextion::set_component_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco2=\"%s\"", component, color); -} -void Nextion::set_component_coordinates(const char *component, int x, int y) { - this->send_command_printf("%s.xcen=%d", component, x); - this->send_command_printf("%s.ycen=%d", component, y); -} -void Nextion::set_component_font(const char *component, uint8_t font_id) { - this->send_command_printf("%s.font=%d", component, font_id); -} -void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); } + bool Nextion::send_command_printf(const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + char buffer[256]; va_list arg; va_start(arg, format); @@ -79,208 +183,911 @@ bool Nextion::send_command_printf(const char *format, ...) { ESP_LOGW(TAG, "Building command for format '%s' failed!", format); return false; } - this->send_command_no_ack(buffer); - if (!this->ack_()) { - ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer); + + if (this->send_command_(buffer)) { + this->add_no_result_to_queue_("send_command_printf"); + return true; + } + return false; +} + +#ifdef NEXTION_PROTOCOL_LOG +void Nextion::print_queue_members_() { + ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); + ESP_LOGN(TAG, "*******************************************"); + int count = 0; + for (auto *i : this->nextion_queue_) { + if (count++ == 10) + break; + + if (i == nullptr) { + ESP_LOGN(TAG, "Nextion queue is null"); + } else { + ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(), + i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str()); + } + } + ESP_LOGN(TAG, "*******************************************"); +} +#endif + +void Nextion::loop() { + if (!this->check_connect_() || this->is_updating_) + return; + + if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) { + this->ignore_is_setup_ = true; + this->sent_setup_commands_ = true; + this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command. + + this->set_backlight_brightness(this->brightness_); + this->goto_page("0"); + + this->set_auto_wake_on_touch(this->auto_wake_on_touch_); + + if (this->touch_sleep_timeout_ != 0) { + this->set_touch_sleep_timeout(this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + this->set_wake_up_page(this->wake_up_page_); + } + + this->ignore_is_setup_ = false; + } + + this->process_serial_(); // Receive serial data + this->process_nextion_commands_(); // Process nextion return commands + + if (!this->nextion_reports_is_setup_) { + if (this->started_ms_ == 0) + this->started_ms_ = millis(); + + if (this->started_ms_ + this->startup_override_ms_ < millis()) { + ESP_LOGD(TAG, "Manually set nextion report ready"); + this->nextion_reports_is_setup_ = true; + } + } +} + +bool Nextion::remove_from_q_(bool report_empty) { + if (this->nextion_queue_.empty()) { + if (report_empty) + ESP_LOGE(TAG, "Nextion queue is empty!"); return false; } + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + delete nb; + this->nextion_queue_.pop_front(); return true; } -void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); } -void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); } -void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); } -void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); } -void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { - this->send_command_printf("add %d,%u,%u", component_id, channel_number, value); -} -void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color); -} -void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { - this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); -} -void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); -} -void Nextion::circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color); -} -void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color); -} -bool Nextion::read_until_ack_() { - while (this->available() >= 4) { - // flush preceding filler bytes - uint8_t temp; - while (this->available() && this->peek_byte(&temp) && temp == 0xFF) - this->read_byte(&temp); - if (!this->available()) - break; +void Nextion::process_serial_() { + uint8_t d; - uint8_t event; - // event type - this->read_byte(&event); + while (this->available()) { + read_byte(&d); + this->command_data_ += d; + } +} +// nextion.tech/instruction-set/ +void Nextion::process_nextion_commands_() { + if (this->command_data_.length() == 0) { + return; + } - uint8_t data[255]; - // total length of data (including end bytes) - uint8_t data_length = 0; - // message is terminated by three consecutive 0xFF - // this variable keeps track of ohow many of those have - // been received - uint8_t end_length = 0; - while (this->available() && end_length < 3 && data_length < sizeof(data)) { - uint8_t byte; - this->read_byte(&byte); - if (byte == 0xFF) { - end_length++; - } else { - end_length = 0; - } - data[data_length++] = byte; + size_t to_process_length = 0; + std::string to_process; + + ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length()); +#ifdef NEXTION_PROTOCOL_LOG + this->print_queue_members_(); +#endif + while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) { + ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size()); + while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() && + static_cast(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) { + ++to_process_length; + ESP_LOGN(TAG, "Add extra 0xFF to process"); } - if (end_length != 3) { - ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!"); - continue; - } + this->nextion_event_ = this->command_data_[0]; - data_length -= 3; // remove filler bytes + to_process_length -= 1; + to_process = this->command_data_.substr(1, to_process_length); - bool invalid_data_length = false; - switch (event) { - case 0x01: // successful execution of instruction (ACK) - return true; - case 0x00: // invalid instruction + switch (this->nextion_event_) { + case 0x00: // instruction sent by user has failed ESP_LOGW(TAG, "Nextion reported invalid instruction!"); + this->remove_from_q_(); + break; - case 0x02: // component ID invalid - ESP_LOGW(TAG, "Nextion reported component ID invalid!"); + case 0x01: // instruction sent by user was successful + + ESP_LOGVV(TAG, "instruction sent by user was successful"); + ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False"); + + this->remove_from_q_(); + if (!this->is_setup_) { + if (this->nextion_queue_.empty()) { + ESP_LOGD(TAG, "Nextion is setup"); + this->is_setup_ = true; + this->setup_callback_.call(); + } + } + break; - case 0x03: // page ID invalid + case 0x02: // invalid Component ID or name was used + this->remove_from_q_(); + break; + case 0x03: // invalid Page ID or name was used ESP_LOGW(TAG, "Nextion reported page ID invalid!"); + this->remove_from_q_(); break; - case 0x04: // picture ID invalid + case 0x04: // invalid Picture ID was used ESP_LOGW(TAG, "Nextion reported picture ID invalid!"); + this->remove_from_q_(); break; - case 0x05: // font ID invalid + case 0x05: // invalid Font ID was used ESP_LOGW(TAG, "Nextion reported font ID invalid!"); + this->remove_from_q_(); break; - case 0x11: // baud rate setting invalid + case 0x06: // File operation fails + ESP_LOGW(TAG, "Nextion File operation fail!"); + break; + case 0x09: // Instructions with CRC validation fails their CRC check + ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!"); + break; + case 0x11: // invalid Baud rate was used ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; - case 0x12: // curve control ID number or channel number is invalid - ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!"); + case 0x12: // invalid Waveform ID or Channel # was used + + if (!this->nextion_queue_.empty()) { + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", + component->get_component_id(), component->get_wave_channel_id()); + + ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", + component->get_component_id(), component->get_wave_channel_id()); + + found = index; + + delete component; + delete nb; + + break; + } + ++index; + } + + if (found != -1) { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } else { + ESP_LOGW( + TAG, + "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); + } + } break; case 0x1A: // variable name invalid - ESP_LOGW(TAG, "Nextion reported variable name invalid!"); + this->remove_from_q_(); + break; case 0x1B: // variable operation invalid ESP_LOGW(TAG, "Nextion reported variable operation invalid!"); + this->remove_from_q_(); break; case 0x1C: // failed to assign ESP_LOGW(TAG, "Nextion reported failed to assign variable!"); + this->remove_from_q_(); break; case 0x1D: // operate EEPROM failed ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!"); break; case 0x1E: // parameter quantity invalid ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!"); + this->remove_from_q_(); break; case 0x1F: // IO operation failed ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!"); break; case 0x20: // undefined escape characters ESP_LOGW(TAG, "Nextion reported undefined escape characters!"); + this->remove_from_q_(); break; case 0x23: // too long variable name ESP_LOGW(TAG, "Nextion reported too long variable name!"); + this->remove_from_q_(); + + break; + case 0x24: // Serial Buffer overflow occurs + ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); break; case 0x65: { // touch event return data - if (data_length != 3) { - invalid_data_length = true; + if (to_process_length != 3) { + ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length); + break; } - uint8_t page_id = data[0]; - uint8_t component_id = data[1]; - uint8_t touch_event = data[2]; // 0 -> release, 1 -> press + uint8_t page_id = to_process[0]; + uint8_t component_id = to_process[1]; + uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { - touch->process(page_id, component_id, touch_event); + touch->process_touch(page_id, component_id, touch_event != 0); } break; } - case 0x67: - case 0x68: { // touch coordinate data - if (data_length != 5) { - invalid_data_length = true; + case 0x67: { // Touch Coordinate (awake) + break; + } + case 0x68: { // touch coordinate data (sleep) + + if (to_process_length != 5) { + ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length); + ESP_LOGW(TAG, "%s", to_process.c_str()); break; } - uint16_t x = (uint16_t(data[0]) << 8) | data[1]; - uint16_t y = (uint16_t(data[2]) << 8) | data[3]; - uint8_t touch_event = data[4]; // 0 -> release, 1 -> press + + uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; + uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; + uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); break; } - case 0x66: // sendme page id + case 0x66: { + break; + } // sendme page id + + // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF + // Returned when using get command for a string. + // Each byte is converted to char. + // data: ab123 case 0x70: // string variable data return + { + if (this->nextion_queue_.empty()) { + ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty"); + break; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { + ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", + component->get_variable_name().c_str()); + } else { + ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(), + component->get_variable_name().c_str(), component->get_queue_type_string().c_str()); + component->set_state_from_string(to_process, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF + // Returned when get command to return a number + // 4 byte 32-bit value in little endian order. + // (0x01+0x02*256+0x03*65536+0x04*16777216) + // data: 67305985 case 0x71: // numeric variable data return - case 0x86: // device automatically enters into sleep mode + { + if (this->nextion_queue_.empty()) { + ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty"); + break; + } + + if (to_process_length == 0) { + ESP_LOGE(TAG, "ERROR: Received numeric return but no data!"); + break; + } + + int dataindex = 0; + + int value = 0; + + for (int i = 0; i < 4; ++i) { + value += to_process[i] << (8 * i); + ++dataindex; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::SENSOR && + component->get_queue_type() != NextionQueueType::BINARY_SENSOR && + component->get_queue_type() != NextionQueueType::SWITCH) { + ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d", + component->get_variable_name().c_str(), component->get_queue_type()); + } else { + ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d", + component->get_variable_name().c_str(), component->get_queue_type(), + component->get_queue_type_string().c_str(), value); + component->set_state_from_int(value, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + + case 0x86: { // device automatically enters into sleep mode + ESP_LOGVV(TAG, "Received Nextion entering sleep automatically"); + this->is_sleeping_ = true; + this->sleep_callback_.call(); + break; + } case 0x87: // device automatically wakes up + { + ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); + this->is_sleeping_ = false; + this->wake_callback_.call(); + break; + } case 0x88: // system successful start up - case 0x89: // start SD card upgrade - case 0xFD: // data transparent transmit finished - case 0xFE: // data transparent transmit ready + { + ESP_LOGD(TAG, "system successful start up %zu", to_process_length); + this->nextion_reports_is_setup_ = true; break; + } + case 0x89: { // start SD card upgrade + break; + } + // Data from nextion is + // 0x90 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x90: { // Switched component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0); + + for (auto *switchtype : this->switchtype_) { + switchtype->process_bool(variable_name, to_process[index] != 0); + } + break; + } + // Data from nextion is + // 0x91 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x71 return data: prints temp1.val,0 + // FF FF FF - End + case 0x91: { // Sensor component + std::string variable_name; + uint8_t index = 0; + + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + index = to_process.find('\0'); + variable_name = to_process.substr(0, index); + // // Get variable name + int value = 0; + for (int i = 0; i < 4; ++i) { + value += to_process[i + index + 1] << (8 * i); + } + + ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value); + + for (auto *sensor : this->sensortype_) { + sensor->process_sensor(variable_name, value); + } + break; + } + + // Data from nextion is + // 0x92 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0 + // 00 - NULL + // FF FF FF - End + case 0x92: { // Text Sensor Component + std::string variable_name; + std::string text_value; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + text_value = to_process.substr(index); + + ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str()); + + // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue; + // nq->variable_name = variable_name; + // nq->state = text_value; + // this->textsensorq_.push_back(nq); + for (auto *textsensortype : this->textsensortype_) { + textsensortype->process_text(variable_name, text_value); + } + break; + } + // Data from nextion is + // 0x93 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x93: { // Binary Sensor component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0); + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->process_bool(&variable_name[0], to_process[index] != 0); + } + break; + } + case 0xFD: { // data transparent transmit finished + ESP_LOGVV(TAG, "Nextion reported data transmit finished!"); + break; + } + case 0xFE: { // data transparent transmit ready + ESP_LOGVV(TAG, "Nextion reported ready for transmit!"); + + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + auto component = nb->component; + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() + : 255; // ADDT command can only send 255 + + this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); + + ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", + component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); + + if (component->get_wave_buffer().size() <= 255) { + component->get_wave_buffer().clear(); + } else { + component->get_wave_buffer().erase(component->get_wave_buffer().begin(), + component->get_wave_buffer().begin() + buffer_to_send); + } + found = index; + delete component; + delete nb; + break; + } + ++index; + } + + if (found == -1) { + ESP_LOGE(TAG, "No waveforms in queue to send data!"); + break; + } else { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } + break; + } default: - ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event); + ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_); break; } - if (invalid_data_length) { - ESP_LOGW(TAG, "Invalid data length from nextion!"); + + // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1); + this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); + } + + uint32_t ms = millis(); + + if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { + for (int i = 0; i < this->nextion_queue_.size(); i++) { + NextionComponentBase *component = this->nextion_queue_[i]->component; + if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { + if (this->nextion_queue_[i]->queue_time == 0) + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", + component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + + delete this->nextion_queue_[i]; + + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + + } else { + break; + } + } + } + ESP_LOGN(TAG, "Loop End"); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); +} // namespace nextion + +void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) { + this->set_nextion_sensor_state(static_cast(queue_type), name, state); +} + +void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) { + ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type); + + switch (queue_type) { + case NextionQueueType::SENSOR: { + for (auto *sensor : this->sensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } + break; + } + case NextionQueueType::BINARY_SENSOR: { + for (auto *sensor : this->binarysensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + case NextionQueueType::SWITCH: { + for (auto *sensor : this->switchtype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + default: { + ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type); + } + } +} + +void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { + ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str()); + + for (auto *sensor : this->textsensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } +} + +void Nextion::all_components_send_state_(bool force_update) { + ESP_LOGD(TAG, "all_components_send_state_ "); + for (auto *binarysensortype : this->binarysensortype_) { + if (force_update || binarysensortype->get_needs_to_send_update()) + binarysensortype->send_state_to_nextion(); + } + for (auto *sensortype : this->sensortype_) { + if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0) + sensortype->send_state_to_nextion(); + } + for (auto *switchtype : this->switchtype_) { + if (force_update || switchtype->get_needs_to_send_update()) + switchtype->send_state_to_nextion(); + } + for (auto *textsensortype : this->textsensortype_) { + if (force_update || textsensortype->get_needs_to_send_update()) + textsensortype->send_state_to_nextion(); + } +} + +void Nextion::update_components_by_prefix(const std::string &prefix) { + for (auto *binarysensortype : this->binarysensortype_) { + if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos) + binarysensortype->update_component_settings(true); + } + for (auto *sensortype : this->sensortype_) { + if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos) + sensortype->update_component_settings(true); + } + for (auto *switchtype : this->switchtype_) { + if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos) + switchtype->update_component_settings(true); + } + for (auto *textsensortype : this->textsensortype_) { + if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos) + textsensortype->update_component_settings(true); + } +} + +uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) { + uint16_t ret = 0; + uint8_t c = 0; + uint8_t nr_of_ff_bytes = 0; + uint64_t start; + bool exit_flag = false; + bool ff_flag = false; + + start = millis(); + + while ((timeout == 0 && this->available()) || millis() - start <= timeout) { + this->read_byte(&c); + if (c == 0xFF) + nr_of_ff_bytes++; + else { + nr_of_ff_bytes = 0; + ff_flag = false; + } + + if (nr_of_ff_bytes >= 3) + ff_flag = true; + + response += (char) c; + if (recv_flag) { + if (response.find(0x05) != std::string::npos) { + exit_flag = true; + } + } + App.feed_wdt(); + delay(1); + + if (exit_flag || ff_flag) { + break; } } - return false; + if (ff_flag) + response = response.substr(0, response.length() - 3); // Remove last 3 0xFF + + ret = response.length(); + return ret; } -void Nextion::loop() { - while (this->available() >= 4) { - this->read_until_ack_(); + +/** + * @brief + * + * @param variable_name Name for the queue + */ +void Nextion::add_no_result_to_queue_(const std::string &variable_name) { + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component->set_variable_name(variable_name); + + nextion_queue->queue_time = millis(); + + this->nextion_queue_.push_back(nextion_queue); + + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param command + */ +void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) { + if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty()) + return; + + if (this->send_command_(command)) { + this->add_no_result_to_queue_(variable_name); } } -#ifdef USE_TIME -void Nextion::set_nextion_rtc_time(time::ESPTime time) { - this->send_command_printf("rtc0=%u", time.year); - this->send_command_printf("rtc1=%u", time.month); - this->send_command_printf("rtc2=%u", time.day_of_month); - this->send_command_printf("rtc3=%u", time.hour); - this->send_command_printf("rtc4=%u", time.minute); - this->send_command_printf("rtc5=%u", time.second); -} -#endif -void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); } -void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); } +bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, + ...) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return false; -void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } -void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + char buffer[256]; va_list arg; va_start(arg, format); - char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); - if (ret > 0) - this->set_component_text(component, buffer); -} -void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; } + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } -void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) { - if (this->page_id_ == page_id && this->component_id_ == component_id) { - this->publish_state(on); + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief Sends a formatted command to the nextion + * + * @param variable_name Variable name for the queue + * @param format The printf-style command format, like "vis %s,0" + * @param ... The format arguments + */ +bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + + char buffer[256]; + va_list arg; + va_start(arg, format); + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } + + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ + +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} + +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(), + state_value); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Sting value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(), + state_value.c_str()); +} + +void Nextion::add_to_get_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = component; + nextion_queue->queue_time = millis(); + + ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + std::string command = "get " + component->get_variable_name_to_send(); + + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); } } +/** + * @brief Add addt command to the queue + * + * @param component_id The waveform component id + * @param wave_chan_id The waveform channel to send it to + * @param buffer_to_send The buffer size + * @param buffer_size The buffer data + */ +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->queue_time = millis(); + + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() + : 255; // ADDT command can only send 255 + + std::string command = "addt " + to_string(component->get_component_id()) + "," + + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); + } +} + +void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } + +ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect") +void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); } + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index a55ff747ee..2389cc6235 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1,9 +1,21 @@ #pragma once -#include "esphome/core/component.h" +#include #include "esphome/core/defines.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "nextion_base.h" +#include "nextion_component.h" +#include "esphome/components/display/display_color_utils.h" + +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#include +#include +#endif +#endif #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" @@ -12,12 +24,14 @@ namespace esphome { namespace nextion { -class NextionTouchComponent; class Nextion; +class NextionComponentBase; using nextion_writer_t = std::function; -class Nextion : public PollingComponent, public uart::UARTDevice { +static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; + +class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: /** * Set the text of a component to a static string. @@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture) { - this->send_command_printf("%s.val=%s", component, picture); - } + void set_component_picture(const char *component, const char *picture); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_background_color("button", 0xFF0000); + * ``` + * + * This will change the background color of the component `button` to red. + */ + void set_component_background_color(const char *component, uint32_t color); /** * Set the background color of a component. * @param component The component name. @@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_background_color("button", "17013"); + * it.set_component_background_color("button", "RED"); * ``` * * This will change the background color of the component `button` to blue. @@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_background_color(const char *component, const char *color); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_background_color("button", color); + * ``` + * + * This will change the background color of the component `button` to what color contains. + */ + void set_component_background_color(const char *component, Color color) override; + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as a int). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", 0xFF0000 ); + * ``` + * + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. + */ + void set_component_pressed_background_color(const char *component, uint32_t color); /** * Set the pressed background color of a component. * @param component The component name. @@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_background_color("button", "17013"); + * it.set_component_pressed_background_color("button", "RED"); * ``` * * This will change the pressed background color of the component `button` to blue. This is the background color that @@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void set_component_pressed_background_color(const char *component, const char *color); + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", color); + * ``` + * + * This will change the pressed background color of the component `button` to blue. This is the background color that + * is shown when the component is pressed. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void set_component_pressed_background_color(const char *component, Color color) override; + + /** + * Set the picture id of a component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_pic("textview", 1); + * ``` + * + * This will change the picture id of the component `textview`. + */ + void set_component_pic(const char *component, uint8_t pic_id); + /** + * Set the background picture id of component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_picc("textview", 1); + * ``` + * + * This will change the background picture id of the component `textview`. + */ + void set_component_picc(const char *component, uint8_t pic_id); + + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t ). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", 0xFF0000); + * ``` + * + * This will change the font color of the component `textview` to a red color. + */ + void set_component_font_color(const char *component, uint32_t color); /** * Set the font color of a component. * @param component The component name. @@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_font_color("textview", "17013"); + * it.set_component_font_color("textview", "RED"); * ``` * * This will change the font color of the component `textview` to a blue color. @@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_font_color(const char *component, const char *color); + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", color); + * ``` + * + * This will change the font color of the component `textview` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_font_color(const char *component, Color color) override; + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", 0xFF0000); + * ``` + * + * This will change the pressed font color of the component `button` to a red. + */ + void set_component_pressed_font_color(const char *component, uint32_t color); /** * Set the pressed font color of a component. * @param component The component name. @@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_font_color("button", "17013"); + * it.set_component_pressed_font_color("button", "RED"); * ``` * * This will change the pressed font color of the component `button` to a blue color. @@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_pressed_font_color(const char *component, const char *color); + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", color); + * ``` + * + * This will change the pressed font color of the component `button` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_pressed_font_color(const char *component, Color color) override; /** * Set the coordinates of a component on screen. * @param component The component name. @@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ - void set_component_font(const char *component, uint8_t font_id); + void set_component_font(const char *component, uint8_t font_id) override; #ifdef USE_TIME /** * Send the current time to the nextion display. @@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Hides the component named `button`. */ - void hide_component(const char *component); + void hide_component(const char *component) override; /** * Show a component. * @param component The component name. @@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Shows the component named `button`. */ - void show_component(const char *component); + void show_component(const char *component) override; /** * Enable touch for a component. * @param component The component name. @@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param value The value to write. */ void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value); + void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value); /** * Display a picture at coordinates. * @param picture_id The picture id. @@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * fill_area(50, 50, 100, 100, "17013"); + * fill_area(50, 50, 100, 100, "RED"); * ``` * * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with @@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * convert color codes to Nextion HMI colors */ void fill_area(int x1, int y1, int width, int height, const char *color); + /** + * Fill a rectangle with a color. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width to draw. + * @param height The height to draw. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, color); + * ``` + * + * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to + * convert color codes to Nextion HMI colors + */ + void fill_area(int x1, int y1, int width, int height, Color color); /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void line(int x1, int y1, int x2, int y2, const char *color); + /** + * Draw a line on the screen. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param x2 The ending x coordinate. + * @param y2 The ending y coordinate. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, "17013"); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void line(int x1, int y1, int x2, int y2, Color color); /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void rectangle(int x1, int y1, int width, int height, const char *color); + /** + * Draw a rectangle outline. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, "17013"); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void rectangle(int x1, int y1, int width, int height, Color color); /** * Draw a circle outline * @param center_x The center x coordinate. @@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param color The color to draw with (as a string). */ void circle(int center_x, int center_y, int radius, const char *color); + /** + * Draw a circle outline + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). + */ + void circle(int center_x, int center_y, int radius, Color color); /** * Draw a filled circled. * @param center_x The center x coordinate. @@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void filled_circle(int center_x, int center_y, int radius, const char *color); - - /** Set the brightness of the backlight. - * - * @param brightness The brightness, from 0 to 100. + /** + * Draw a filled circled. + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). * * Example: * ```cpp - * it.set_backlight_brightness(30); + * it.filled_cricle(25, 25, 10, color); + * ``` + * + * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void filled_circle(int center_x, int center_y, int radius, Color color); + + /** Set the brightness of the backlight. + * + * @param brightness The brightness percentage from 0 to 1.0. + * + * Example: + * ```cpp + * it.set_backlight_brightness(.3); * ``` * * Changes the brightness of the display to 30%. */ - void set_backlight_brightness(uint8_t brightness); + void set_backlight_brightness(float brightness); /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. @@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); + /** + * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. + * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to + * wakes up to current page. + * + * Example: + * ```cpp + * it.set_wake_up_page(2); + * ``` + * + * The display will wake up to page 2. + */ + void set_wake_up_page(uint8_t page_id = 255); + /** + * Sets if Nextion should auto-wake from sleep when touch press occurs. + * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, + * the first touch will only trigger the auto wake mode and not trigger a Touch Event. + * + * Example: + * ```cpp + * it.set_auto_wake_on_touch(true); + * ``` + * + * The display will wake up by touch. + */ + void set_auto_wake_on_touch(bool auto_wake); + /** + * Sets Nextion mode between sleep and awake + * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode. + */ + void sleep(bool sleep); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); } + void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); } + void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); } + void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); } + void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); } + void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); } + void setup() override; void set_brightness(float brightness) { this->brightness_ = brightness; } float get_setup_priority() const override; @@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice { void loop() override; void set_writer(const nextion_writer_t &writer); - /** - * Manually send a raw command to the display and don't wait for an acknowledgement packet. - * @param command The command to write, for example "vis b0,0". - */ - void send_command_no_ack(const char *command); + // This function has been deprecated + void set_wait_for_ack(bool wait_for_ack); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice { */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); - void set_wait_for_ack(bool wait_for_ack); +#ifdef USE_TFT_UPLOAD + /** + * Set the tft file URL. https seems problamtic with arduino.. + */ + void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } + +#endif + + /** + * Upload the tft file and softreset the Nextion + */ + void upload_tft(); + void dump_config() override; + + /** + * Softreset the Nextion + */ + void soft_reset(); + + /** Add a callback to be notified of sleep state changes. + * + * @param callback The void() callback. + */ + void add_sleep_state_callback(std::function &&callback); + + /** Add a callback to be notified of wake state changes. + * + * @param callback The void() callback. + */ + void add_wake_state_callback(std::function &&callback); + + /** Add a callback to be notified when the nextion completes its initialize setup. + * + * @param callback The void() callback. + */ + void add_setup_state_callback(std::function &&callback); + + void update_all_components(); + + /** + * @brief Set the nextion sensor state object. + * + * @param[in] queue_type + * Index of NextionQueueType. + * + * @param[in] name + * Component/variable name. + * + * @param[in] state + * State to set. + */ + void set_nextion_sensor_state(int queue_type, const std::string &name, float state); + void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); + void set_nextion_text_state(const std::string &name, const std::string &state); + + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + int state_value) override; + + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + const std::string &state_value) override; + + void add_to_get_queue(NextionComponentBase *component) override; + + void add_addt_command_to_queue(NextionComponentBase *component) override; + + void update_components_by_prefix(const std::string &prefix); + + void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { + this->touch_sleep_timeout_ = touch_sleep_timeout; + } + void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } + void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - bool ack_(); - bool read_until_ack_(); + std::deque nextion_queue_; + uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); + void all_components_send_state_(bool force_update = false); + uint64_t comok_sent_ = 0; + bool remove_from_q_(bool report_empty = true); + /** + * @brief + * Sends commands ignoring of the Nextion has been setup. + */ + bool ignore_is_setup_ = false; + bool nextion_reports_is_setup_ = false; + uint8_t nextion_event_; + + void process_nextion_commands_(); + void process_serial_(); + bool is_updating_ = false; + uint32_t touch_sleep_timeout_ = 0; + int wake_up_page_ = -1; + bool auto_wake_on_touch_ = true; + + /** + * Manually send a raw command to the display and don't wait for an acknowledgement packet. + * @param command The command to write, for example "vis b0,0". + */ + bool send_command_(const std::string &command); + void add_no_result_to_queue_(const std::string &variable_name); + bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); + + bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe = false); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe = false); + +#ifdef USE_TFT_UPLOAD +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP8266 + WiFiClient *wifi_client_{nullptr}; + BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; + WiFiClient *get_wifi_client_(); +#endif + + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param int contentLength Total size of the file + * @param uint32_t chunk_size + * @return true if success, false for failure. + */ + int content_length_ = 0; + int tft_size_ = 0; + int upload_by_chunks_(HTTPClient *http, int range_start); + + bool upload_with_range_(uint32_t range_start, uint32_t range_end); + + /** + * start update tft file to nextion. + * + * @param const uint8_t *file_buf + * @param size_t buf_size + * @return true if success, false for failure. + */ + bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); + void upload_end_(); + +#endif + +#endif + + bool get_is_connected_() { return this->is_connected_; } + + bool check_connect_(); + + std::vector touch_; + std::vector switchtype_; + std::vector sensortype_; + std::vector textsensortype_; + std::vector binarysensortype_; + CallbackManager setup_callback_{}; + CallbackManager sleep_callback_{}; + CallbackManager wake_callback_{}; - std::vector touch_; optional writer_; - bool wait_for_ack_{true}; float brightness_{1.0}; + + std::string device_model_; + std::string firmware_version_; + std::string serial_number_; + std::string flash_size_; + + void remove_front_no_sensors_(); + +#ifdef USE_TFT_UPLOAD + std::string tft_url_; + uint8_t *transfer_buffer_{nullptr}; + size_t transfer_buffer_size_; + bool upload_first_chunk_sent_ = false; +#endif + +#ifdef NEXTION_PROTOCOL_LOG + void print_queue_members_(); +#endif + void reset_(bool reset_nextion = true); + + std::string command_data_; + bool is_connected_ = false; + uint32_t startup_override_ms_ = 8000; + uint32_t max_q_age_ms_ = 8000; + uint32_t started_ms_ = 0; + bool sent_setup_commands_ = false; }; - -class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff { - public: - void set_page_id(uint8_t page_id) { page_id_ = page_id; } - void set_component_id(uint8_t component_id) { component_id_ = component_id; } - void process(uint8_t page_id, uint8_t component_id, bool on); - - protected: - uint8_t page_id_; - uint8_t component_id_; -}; - } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h new file mode 100644 index 0000000000..a24fd74060 --- /dev/null +++ b/esphome/components/nextion/nextion_base.h @@ -0,0 +1,58 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_component_base.h" +namespace esphome { +namespace nextion { + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define NEXTION_PROTOCOL_LOG +#endif + +#ifdef NEXTION_PROTOCOL_LOG +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__) +#else +#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__) +#endif +#else +#define ESP_LOGN(tag, ...) \ + {} +#endif + +class NextionBase; + +class NextionBase { + public: + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) = 0; + + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) = 0; + + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + + virtual void add_to_get_queue(NextionComponentBase *component) = 0; + + virtual void set_component_background_color(const char *component, Color color) = 0; + virtual void set_component_pressed_background_color(const char *component, Color color) = 0; + virtual void set_component_font_color(const char *component, Color color) = 0; + virtual void set_component_pressed_font_color(const char *component, Color color) = 0; + virtual void set_component_font(const char *component, uint8_t font_id) = 0; + + virtual void show_component(const char *component) = 0; + virtual void hide_component(const char *component) = 0; + + bool is_sleeping() { return this->is_sleeping_; } + bool is_setup() { return this->is_setup_; } + + protected: + bool is_setup_ = false; + bool is_sleeping_ = false; +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp new file mode 100644 index 0000000000..931b934ba2 --- /dev/null +++ b/esphome/components/nextion/nextion_commands.cpp @@ -0,0 +1,234 @@ +#include "nextion.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion"; + +// Sleep safe commands +void Nextion::soft_reset() { this->send_command_("rest"); } + +void Nextion::set_wake_up_page(uint8_t page_id) { + if (page_id > 255) { + ESP_LOGD(TAG, "Wake up page of bounds, range 0-255"); + return; + } + this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); +} + +void Nextion::set_touch_sleep_timeout(uint16_t timeout) { + if (timeout < 3 || timeout > 65535) { + ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); + return; + } + + this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); +} + +void Nextion::sleep(bool sleep) { + if (sleep) { // Set sleep + this->is_sleeping_ = true; + this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true); + } else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off + this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true); + } +} +// End sleep safe commands + +// Set Colors +void Nextion::set_component_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color); +} + +void Nextion::set_component_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color); +} + +void Nextion::set_component_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pic(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id); +} + +void Nextion::set_component_picc(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id); +} + +void Nextion::set_component_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color); +} + +void Nextion::set_component_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color); +} + +void Nextion::set_component_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[256]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + this->set_component_text(component, buffer); +} + +// General Nextion +void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); } + +void Nextion::set_backlight_brightness(float brightness) { + if (brightness < 0 || brightness > 1.0) { + ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0"); + return; + } + this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast(brightness * 100)); +} + +void Nextion::set_auto_wake_on_touch(bool auto_wake) { + this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); +} + +// General Component +void Nextion::set_component_font(const char *component, uint8_t font_id) { + this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id); +} + +void Nextion::hide_component(const char *component) { + this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component); +} + +void Nextion::show_component(const char *component) { + this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component); +} + +void Nextion::enable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component); +} + +void Nextion::disable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); +} + +void Nextion::set_component_picture(const char *component, const char *picture) { + this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +} + +void Nextion::set_component_text(const char *component, const char *text) { + this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text); +} + +void Nextion::set_component_value(const char *component, int value) { + this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value); +} + +void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value); +} + +void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number, + value); +} + +void Nextion::set_component_coordinates(const char *component, int x, int y) { + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x); + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y); +} + +// Drawing +void Nextion::display_picture(int picture_id, int x_start, int y_start) { + this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); +} + +void Nextion::line(int x1, int y1, int x2, int y2, Color color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +#ifdef USE_TIME +void Nextion::set_nextion_rtc_time(time::ESPTime time) { + this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); + this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); + this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); + this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour); + this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); + this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); +} +#endif + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp new file mode 100644 index 0000000000..bbb2cf6cb2 --- /dev/null +++ b/esphome/components/nextion/nextion_component.cpp @@ -0,0 +1,116 @@ +#include "nextion_component.h" + +namespace esphome { +namespace nextion { + +void NextionComponent::set_background_color(Color bco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->bco_ = bco; + this->bco_needs_update_ = true; + this->bco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_background_pressed_color(Color bco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + + this->bco2_ = bco2; + this->bco2_needs_update_ = true; + this->bco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_color(Color pco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco_ = pco; + this->pco_needs_update_ = true; + this->pco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_pressed_color(Color pco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco2_ = pco2; + this->pco2_needs_update_ = true; + this->pco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_font_id(uint8_t font_id) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->font_id_ = font_id; + this->font_id_needs_update_ = true; + this->font_id_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_visible(bool visible) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->visible_ = visible; + this->visible_needs_update_ = true; + this->visible_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::update_component_settings(bool force_update) { + if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ || + (!this->visible_needs_update_ && !this->visible_)) { + this->needs_to_send_update_ = true; + return; + } + + if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) { + std::string name_to_send = this->variable_name_; + + size_t pos = name_to_send.find_last_of('.'); + if (pos != std::string::npos) { + name_to_send = name_to_send.substr(pos + 1); + } + + this->visible_needs_update_ = false; + + if (this->visible_) { + this->nextion_->show_component(name_to_send.c_str()); + this->send_state_to_nextion(); + } else { + this->nextion_->hide_component(name_to_send.c_str()); + return; + } + } + + if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_); + this->bco_needs_update_ = false; + } + if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_); + this->bco2_needs_update_ = false; + } + if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { + this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_); + this->pco_needs_update_ = false; + } + if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { + this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_); + this->pco2_needs_update_ = false; + } + + if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) { + this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_); + this->font_id_needs_update_ = false; + } +} +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.h b/esphome/components/nextion/nextion_component.h new file mode 100644 index 0000000000..2f3c4f3c16 --- /dev/null +++ b/esphome/components/nextion/nextion_component.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionComponent; + +class NextionComponent : public NextionComponentBase { + public: + void update_component_settings() override { this->update_component_settings(false); }; + + void update_component_settings(bool force_update) override; + + void set_background_color(Color bco); + void set_background_pressed_color(Color bco2); + void set_foreground_color(Color pco); + void set_foreground_pressed_color(Color pco2); + void set_font_id(uint8_t font_id); + void set_visible(bool visible); + + protected: + NextionBase *nextion_; + + bool bco_needs_update_ = false; + bool bco_is_set_ = false; + Color bco_; + bool bco2_needs_update_ = false; + bool bco2_is_set_ = false; + Color bco2_; + bool pco_needs_update_ = false; + bool pco_is_set_ = false; + Color pco_; + bool pco2_needs_update_ = false; + bool pco2_is_set_ = false; + Color pco2_; + uint8_t font_id_ = 0; + bool font_id_needs_update_ = false; + bool font_id_is_set_ = false; + + bool visible_ = true; + bool visible_needs_update_ = false; + bool visible_is_set_ = false; + + // void send_state_to_nextion() = 0; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h new file mode 100644 index 0000000000..71ad803bc4 --- /dev/null +++ b/esphome/components/nextion/nextion_component_base.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace nextion { + +enum NextionQueueType { + NO_RESULT = 0, + SENSOR = 1, + BINARY_SENSOR = 2, + SWITCH = 3, + TEXT_SENSOR = 4, + WAVEFORM_SENSOR = 5, +}; + +static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR", + "SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"}; + +class NextionComponentBase; + +class NextionQueue { + public: + virtual ~NextionQueue() = default; + NextionComponentBase *component; + uint32_t queue_time = 0; +}; + +class NextionComponentBase { + public: + virtual ~NextionComponentBase() = default; + + void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") { + variable_name_ = variable_name; + if (variable_name_to_send.empty()) { + variable_name_to_send_ = variable_name; + } else { + variable_name_to_send_ = variable_name_to_send; + } + } + + virtual void update_component_settings(){}; + virtual void update_component_settings(bool force_update){}; + + virtual void update_component(){}; + virtual void process_sensor(const std::string &variable_name, int state){}; + virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){}; + virtual void process_text(const std::string &variable_name, const std::string &text_value){}; + virtual void process_bool(const std::string &variable_name, bool on){}; + + virtual void set_state(float state){}; + virtual void set_state(float state, bool publish){}; + virtual void set_state(float state, bool publish, bool send_to_nextion){}; + + virtual void set_state(bool state){}; + virtual void set_state(bool state, bool publish){}; + virtual void set_state(bool state, bool publish, bool send_to_nextion){}; + + virtual void set_state(const std::string &state) {} + virtual void set_state(const std::string &state, bool publish) {} + virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){}; + + uint8_t get_component_id() { return this->component_id_; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + uint8_t get_wave_channel_id() { return this->wave_chan_id_; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + + std::vector get_wave_buffer() { return this->wave_buffer_; } + size_t get_wave_buffer_size() { return this->wave_buffer_.size(); } + + std::string get_variable_name() { return this->variable_name_; } + std::string get_variable_name_to_send() { return this->variable_name_to_send_; } + virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; } + virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; } + virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){}; + virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){}; + virtual void send_state_to_nextion(){}; + bool get_needs_to_send_update() { return this->needs_to_send_update_; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + + protected: + std::string variable_name_; + std::string variable_name_to_send_; + + uint8_t component_id_ = 0; + uint8_t wave_chan_id_ = UINT8_MAX; + std::vector wave_buffer_; + int wave_max_length_ = 255; + + bool needs_to_send_update_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp new file mode 100644 index 0000000000..6a681af6c5 --- /dev/null +++ b/esphome/components/nextion/nextion_upload.cpp @@ -0,0 +1,343 @@ + +#include "nextion.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_upload"; + +#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) + +// Followed guide +// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 + +int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { + int range_end = 0; + + if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip + range_end = 16384 - 1; + } else { + range_end = range_start + this->transfer_buffer_size_ - 1; + } + + if (range_end > this->tft_size_) + range_end = this->tft_size_; + + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http->setRedirectLimit(3); + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + char range_header[64]; + sprintf(range_header, "bytes=%d-%d", range_start, range_end); + + ESP_LOGD(TAG, "Requesting range: %s", range_header); + + int tries = 1; + int code = 0; + while (tries <= 5) { +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifndef CLANG_TIDY +#ifdef ARDUINO_ARCH_ESP8266 + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + ++tries; + if (!begin_status) { + ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); + continue; + } + + http->addHeader("Range", range_header); + + code = http->GET(); + if (code == 200 || code == 206) { + break; + } + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + http->end(); + App.feed_wdt(); + delay(500); // NOLINT + } + + if (tries > 5) { + return -1; + } + + std::string recv_string; + size_t size = 0; + int sent = 0; + int range = range_end - range_start; + + while (sent < range) { + size = http->getStreamPtr()->available(); + if (!size) { + App.feed_wdt(); + delay(0); + continue; + } + int c = http->getStreamPtr()->readBytes( + &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); + sent += c; + } + http->end(); + ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); + for (uint32_t i = 0; i < range; i += 4096) { + this->write_array(&this->transfer_buffer_[i], 4096); + this->content_length_ -= 4096; + ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, + range_end, range_start); + + if (!this->upload_first_chunk_sent_) { + this->upload_first_chunk_sent_ = true; + delay(500); // NOLINT + App.feed_wdt(); + } + + this->recv_ret_string_(recv_string, 2048, true); + if (recv_string[0] == 0x08) { + uint32_t result = 0; + for (int i = 0; i < 4; ++i) { + result += static_cast(recv_string[i + 1]) << (8 * i); + } + if (result > 0) { + ESP_LOGD(TAG, "Nextion reported new range %d", result); + this->content_length_ = this->tft_size_ - result; + return result; + } + } + recv_string.clear(); + } + return range_end + 1; +} + +void Nextion::upload_tft() { + if (this->is_updating_) { + ESP_LOGD(TAG, "Currently updating"); + return; + } + + if (!network_is_connected()) { + ESP_LOGD(TAG, "network is not connected"); + return; + } + + this->is_updating_ = true; + + HTTPClient http; + http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http.begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(3); + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + if (!begin_status) { + this->is_updating_ = false; + ESP_LOGD(TAG, "connection failed"); +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) + free(this->transfer_buffer_); + else +#endif + delete this->transfer_buffer_; + return; + } else { + ESP_LOGD(TAG, "Connected"); + } + + http.addHeader("Range", "bytes=0-255"); + const char *header_names[] = {"Content-Range"}; + http.collectHeaders(header_names, 1); + ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); + + http.setReuse(true); + // try up to 5 times. DNS sometimes needs a second try or so + int tries = 1; + int code = http.GET(); + delay(100); // NOLINT + + App.feed_wdt(); + while (code != 200 && code != 206 && tries <= 5) { + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + + delay(250); // NOLINT + App.feed_wdt(); + code = http.GET(); + ++tries; + } + + if ((code != 200 && code != 206) || tries > 5) { + this->upload_end_(); + } + + String content_range_string = http.header("Content-Range"); + content_range_string.remove(0, 12); + this->content_length_ = content_range_string.toInt(); + this->tft_size_ = content_length_; + http.end(); + + if (this->content_length_ < 4096) { + ESP_LOGE(TAG, "Failed to get file size"); + this->upload_end_(); + } + + ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); + // The Nextion will ignore the update command if it is sleeping + + this->send_command_("sleep=0"); + this->set_backlight_brightness(1.0); + delay(250); // NOLINT + + App.feed_wdt(); + + char command[128]; + // Tells the Nextion the content length of the tft file and baud rate it will be sent at + // Once the Nextion accepts the command it will wait until the file is successfully uploaded + // If it fails for any reason a power cycle of the display will be needed + sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate()); + + // Clear serial receive buffer + uint8_t d; + while (this->available()) { + this->read_byte(&d); + }; + + this->send_command_(command); + + App.feed_wdt(); + + std::string response; + ESP_LOGD(TAG, "Waiting for upgrade response"); + this->recv_ret_string_(response, 2000, true); // This can take some time to return + + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); + + for (int i = 0; i < response.length(); i++) { + ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); + } + + if (response.find(0x05) != std::string::npos) { + ESP_LOGD(TAG, "preparation for tft update done"); + } else { + ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str()); + this->upload_end_(); + } + + // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 +#ifdef ARDUINO_ARCH_ESP32 + uint32_t chunk_size = 8192; + if (psramFound()) { + chunk_size = this->content_length_; + } else { + if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand + int chunk = int((ESP.getFreeHeap() - 32768) / 4096); + chunk_size = chunk * 4096; + chunk_size = chunk_size > 65536 ? 65536 : chunk_size; + } else if (ESP.getFreeHeap() < 10240) { + chunk_size = 4096; + } + } +#else + uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; +#endif + + if (this->transfer_buffer_ == nullptr) { +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) { + ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); + this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); + if (this->transfer_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size); + this->upload_end_(); + } + } else { +#endif + ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); + this->transfer_buffer_ = new uint8_t[chunk_size]; + if (!this->transfer_buffer_) { // Try a smaller size + ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); + chunk_size = 4096; + ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); + this->transfer_buffer_ = new uint8_t[chunk_size]; + + if (!this->transfer_buffer_) + this->upload_end_(); +#ifdef ARDUINO_ARCH_ESP32 + } +#endif + } + + this->transfer_buffer_size_ = chunk_size; + } + + ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", + this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); + + int result = 0; + while (this->content_length_ > 0) { + result = this->upload_by_chunks_(&http, result); + if (result < 0) { + ESP_LOGD(TAG, "Error updating Nextion!"); + this->upload_end_(); + } + App.feed_wdt(); + ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); + } + ESP_LOGD(TAG, "Succesfully updated Nextion!"); + + this->upload_end_(); +} + +void Nextion::upload_end_() { + ESP_LOGD(TAG, "Restarting Nextion"); + this->soft_reset(); + delay(1500); // NOLINT + ESP_LOGD(TAG, "Restarting esphome"); + ESP.restart(); +} + +#ifdef ARDUINO_ARCH_ESP8266 +WiFiClient *Nextion::get_wifi_client_() { + if (this->tft_url_.compare(0, 6, "https:") == 0) { + if (this->wifi_client_secure_ == nullptr) { + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_->setInsecure(); + this->wifi_client_secure_->setBufferSizes(512, 512); + } + return this->wifi_client_secure_; + } + + if (this->wifi_client_ == nullptr) { + this->wifi_client_ = new WiFiClient(); + } + return this->wifi_client_; +} +#endif + +#else +void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } +#endif +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py new file mode 100644 index 0000000000..f8a383e3ac --- /dev/null +++ b/esphome/components/nextion/sensor/__init__.py @@ -0,0 +1,99 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor + +from esphome.const import ( + CONF_ID, + UNIT_EMPTY, + ICON_EMPTY, + CONF_COMPONENT_ID, + DEVICE_CLASS_EMPTY, +) +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_SENSOR_COMPONENT_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, + CONF_PRECISION, + CONF_WAVE_CHANNEL_ID, + CONF_WAVE_MAX_VALUE, + CONF_WAVEFORM_SEND_LAST_VALUE, + CONF_WAVE_MAX_LENGTH, +) + + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) + + +def CheckWaveID(value): + value = cv.int_(value) + if value < 0 or value > 3: + raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3") + return value + + +def _validate(config): + if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config: + raise cv.Invalid( + f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set" + ) + + return config + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(NextionSensor), + cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), + cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean, + } + ) + .extend(CONFIG_SENSOR_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), + _validate, +) + + +async def to_code(config): + + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(hub.register_sensor_component(var)) + + await setup_component_core_(var, config, ".val") + + if CONF_PRECISION in config: + cg.add(var.set_precision(config[CONF_PRECISION])) + + if CONF_COMPONENT_ID in config: + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + + if CONF_WAVE_CHANNEL_ID in config: + cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID])) + + if CONF_WAVEFORM_SEND_LAST_VALUE in config: + cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])) + + if CONF_WAVE_MAX_VALUE in config: + cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) + + if CONF_WAVE_MAX_LENGTH in config: + cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp new file mode 100644 index 0000000000..bbcb465d85 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -0,0 +1,110 @@ +#include "nextion_sensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_sensor"; + +void NextionSensor::process_sensor(const std::string &variable_name, int state) { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state); + } +} + +void NextionSensor::add_to_wave_buffer(float state) { + this->needs_to_send_update_ = true; + + int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100); + + wave_buffer_.push_back(wave_state); + + if (this->wave_buffer_.size() > this->wave_max_length_) { + this->wave_buffer_.erase(this->wave_buffer_.begin()); + } +} + +void NextionSensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + this->nextion_->add_to_get_queue(this); + } else { + if (this->send_last_value_) { + this->add_to_wave_buffer(this->last_value_); + } + + this->wave_update_(); + } +} + +void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (isnan(state)) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + + if (this->precision_ > 0) { + double to_multiply = pow(10, this->precision_); + int state_value = (int) (state * to_multiply); + + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + } else { + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + } + } else { + if (this->send_last_value_) { + this->last_value_ = state; // Update will handle setting the buffer + } else { + this->add_to_wave_buffer(state); + } + } + + if (this->wave_chan_id_ == UINT8_MAX) { + if (publish) { + this->publish_state(state); + } else { + this->raw_state = state; + this->state = state; + this->has_state_ = true; + } + } + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); +} + +void NextionSensor::wave_update_() { + if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) { + return; + } + +#ifdef NEXTION_PROTOCOL_LOG + size_t buffer_to_send = + this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255 + + ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d", + buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); +#endif + + this->nextion_->add_addt_command_to_queue(this); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h new file mode 100644 index 0000000000..e4dde9a513 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSensor; + +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { + public: + NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + + void update_component() override { this->update(); } + void update() override; + void add_to_wave_buffer(float state); + void set_precision(uint8_t precision) { this->precision_ = precision; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } + void process_sensor(const std::string &variable_name, int state) override; + + void set_state(float state) override { this->set_state(state, true, true); } + void set_state(float state, bool publish) override { this->set_state(state, publish, true); } + void set_state(float state, bool publish, bool send_to_nextion) override; + + void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + NextionQueueType get_queue_type() override { + return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; + } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } + + protected: + uint8_t precision_ = 0; + uint32_t wave_maxvalue_ = 255; + + float last_value_ = 0; + bool send_last_value_ = true; + void wave_update_(); +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py new file mode 100644 index 0000000000..068681fa14 --- /dev/null +++ b/esphome/components/nextion/switch/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch + +from esphome.const import CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + CONFIG_SWITCH_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionSwitch), + } + ) + .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + cg.add(hub.register_switch_component(var)) + + await setup_component_core_(var, config, ".val") diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp new file mode 100644 index 0000000000..1f32ad3425 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -0,0 +1,52 @@ +#include "nextion_switch.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_switch"; + +void NextionSwitch::process_bool(const std::string &variable_name, bool on) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(on); + + ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionSwitch::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + if (publish) { + this->publish_state(state); + } else { + this->state = state; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state)); +} + +void NextionSwitch::write_state(bool state) { this->set_state(state); } + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h new file mode 100644 index 0000000000..1548287473 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSwitch; + +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { + public: + NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } + + void update() override; + void update_component() override { this->update(); } + void process_bool(const std::string &variable_name, bool on) override; + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + void write_state(bool state) override; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py new file mode 100644 index 0000000000..9c170dd807 --- /dev/null +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -0,0 +1,38 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID + +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_TEXT_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionTextSensor = nextion_ns.class_( + "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionTextSensor), + } + ) + .extend(CONFIG_TEXT_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + cg.add(hub.register_textsensor_component(var)) + + await setup_component_core_(var, config, ".txt") diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp new file mode 100644 index 0000000000..08f032df74 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -0,0 +1,49 @@ +#include "nextion_textsensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_textsensor"; + +void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(text_value); + ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str()); + } +} + +void NextionTextSensor::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->nextion_->add_no_result_to_queue_with_set(this, state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str()); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h new file mode 100644 index 0000000000..5716d0a008 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -0,0 +1,32 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionTextSensor; + +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { + public: + NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void update() override; + void update_component() override { this->update(); } + void on_state_changed(const std::string &state); + + void process_text(const std::string &variable_name, const std::string &text_value) override; + + void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); } + void set_state(const std::string &state) override { this->set_state(state, true, true); } + void set_state(const std::string &state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; } + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 6dd62070da..cd54290c73 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -56,6 +56,7 @@ class ESP8266SoftwareSerial { class UARTComponent : public Component, public Stream { public: void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_config(); diff --git a/script/ci-custom.py b/script/ci-custom.py index 02f193c6e0..d79e5b5e2f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from helpers import git_ls_files, filter_changed import codecs import collections import fnmatch @@ -12,7 +13,6 @@ import functools import argparse sys.path.append(os.path.dirname(__file__)) -from helpers import git_ls_files, filter_changed def find_all(a_str, sub): @@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match): "esphome/components/number/number.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", + "esphome/components/nextion/nextion_base.h", "esphome/components/sensor/sensor.h", "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", diff --git a/tests/test1.yaml b/tests/test1.yaml index 1c522a23d4..f9fe7d106d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1055,10 +1055,6 @@ binary_sensor: pin: GPIO27 threshold: 1000 id: btn_left - - platform: nextion - page_id: 0 - component_id: 2 - name: 'Nextion Component 2 Touch' - platform: template name: 'Garage Door Open' id: garage_door @@ -1882,11 +1878,6 @@ display: intensity: 3 lambda: |- it.print("1234"); - - platform: nextion - uart_id: uart0 - lambda: |- - it.set_component_value("gauge", 50); - it.set_component_text("textview", "Hello World!"); - platform: pcd8544 cs_pin: GPIO23 dc_pin: GPIO23 diff --git a/tests/test3.yaml b/tests/test3.yaml index c709539309..acd975d794 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -269,6 +269,7 @@ wled: adalight: + sensor: - platform: apds9960 type: proximity @@ -534,6 +535,15 @@ sensor: export_reactive_energy: name: 'Export Reactive Energy' + - platform: nextion + id: testnumber + name: 'testnumber' + variable_name: testnumber + - platform: nextion + id: testwave + name: 'testwave' + component_id: 2 + wave_channel_id: 1 time: - platform: homeassistant @@ -605,7 +615,14 @@ binary_sensor: binary_sensors: - id: custom_binary_sensor name: Custom Binary Sensor - + - platform: nextion + page_id: 0 + component_id: 2 + name: 'Nextion Component 2 Touch' + - platform: nextion + id: r0_sensor + name: 'R0 Sensor' + component_name: page0.r0 globals: - id: my_global_string type: std::string @@ -653,6 +670,11 @@ text_sensor: text_sensors: - id: custom_text_sensor name: Custom Text Sensor + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 script: - id: my_script @@ -704,6 +726,10 @@ switch: switches: - id: custom_switch name: Custom Switch + - platform: nextion + id: r0 + name: 'R0 Switch' + component_name: page0.r0 custom_component: lambda: |- @@ -1086,6 +1112,16 @@ display: id: my_matrix lambda: |- it.printdigit("hello"); + - platform: nextion + uart_id: uart1 + tft_url: 'http://esphome.io/default35.tft' + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' http_request: useragent: esphome/device From 54eb6070fb47da94e208e9daac60b64273d2fb3c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 13:55:58 +1200 Subject: [PATCH 044/285] Bump version to v1.20.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30c4748933..35f1e560e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 20 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 3715ba030bfa93a396ad2e654c1009018b043eb3 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Wed, 14 Jul 2021 23:45:41 -0400 Subject: [PATCH 045/285] Fix ethernet component hostname handling (#2010) Co-authored-by: Otto Winter --- .gitignore | 3 + .../ethernet/ethernet_component.cpp | 213 +++++++++--------- .../components/ethernet/ethernet_component.h | 10 +- 3 files changed, 120 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index a24550ad54..954ecb2cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ __pycache__/ # Intellij Idea .idea +# Vim +*.swp + # Hide some OS X stuff .DS_Store .AppleDouble diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 22007caafa..52c184eef6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -25,6 +25,13 @@ static const char *const TAG = "ethernet"; EthernetComponent *global_eth_component; +#define ESPHL_ERROR_CHECK(err, message) \ + if (err != ESP_OK) { \ + ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ + this->mark_failed(); \ + return; \ + } + EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); @@ -36,103 +43,6 @@ void EthernetComponent::setup() { this->power_pin_->setup(); } - this->start_connect_(); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif -} -void EthernetComponent::loop() { - const uint32_t now = millis(); - if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) { - ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); - this->start_connect_(); - return; - } - - if (this->connected_ == this->last_connected_) - // nothing changed - return; - - if (this->connected_) { - // connection established - ESP_LOGI(TAG, "Connected via Ethernet!"); - this->dump_connect_params_(); - this->status_clear_warning(); - } else { - // connection lost - ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); - this->start_connect_(); - } - - this->last_connected_ = this->connected_; - - network_tick_mdns(); -} -void EthernetComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Ethernet:"); - this->dump_connect_params_(); - LOG_PIN(" Power Pin: ", this->power_pin_); - ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); - ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); -} -float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); -} - -void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { - const char *event_name; - - switch (event) { - case SYSTEM_EVENT_ETH_START: - event_name = "ETH started"; - break; - case SYSTEM_EVENT_ETH_STOP: - event_name = "ETH stopped"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_CONNECTED: - event_name = "ETH connected"; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - event_name = "ETH disconnected"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_GOT_IP: - event_name = "ETH Got IP"; - this->connected_ = true; - break; - default: - return; - } - - ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); -} - -#define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ - ESP_LOGE(TAG, message ": %d", err); \ - this->mark_failed(); \ - return; \ - } - -void EthernetComponent::start_connect_() { - this->connect_begin_ = millis(); - this->status_set_warning(); - - esp_err_t err; - if (this->initialized_) { - // already initialized - err = esp_eth_enable(); - ESPHL_ERROR_CHECK(err, "ETH enable error"); - return; - } - switch (this->type_) { case ETHERNET_TYPE_LAN8720: { memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); @@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() { tcpipInit(); + esp_err_t err; err = esp_eth_init(&this->eth_config); - if (err != ESP_OK) { - ESP_LOGE(TAG, "ETH init error: %d", err); - this->mark_failed(); - return; + ESPHL_ERROR_CHECK(err, "ETH init error"); + err = esp_eth_enable(); + ESPHL_ERROR_CHECK(err, "ETH enable error"); + +#ifdef USE_MDNS + network_setup_mdns(); +#endif +} +void EthernetComponent::loop() { + const uint32_t now = millis(); + + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting ethernet connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected via Ethernet!"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); + + network_tick_mdns(); + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + } +} +void EthernetComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Ethernet:"); + this->dump_connect_params_(); + LOG_PIN(" Power Pin: ", this->power_pin_); + ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); + ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); + ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); +} +float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } +bool EthernetComponent::can_proceed() { return this->is_connected(); } +IPAddress EthernetComponent::get_ip_address() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.ip.addr); +} + +void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { + const char *event_name; + + switch (event) { + case SYSTEM_EVENT_ETH_START: + event_name = "ETH started"; + this->started_ = true; + break; + case SYSTEM_EVENT_ETH_STOP: + event_name = "ETH stopped"; + this->started_ = false; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_CONNECTED: + event_name = "ETH connected"; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + event_name = "ETH disconnected"; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_GOT_IP: + event_name = "ETH Got IP"; + this->connected_ = true; + break; + default: + return; } - this->initialized_ = true; + ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); +} - tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); +void EthernetComponent::start_connect_() { + this->connect_begin_ = millis(); + this->status_set_warning(); + + esp_err_t err; + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); + ESPHL_ERROR_CHECK(err, "ETH set hostname error"); tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { @@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) { delay(1); global_eth_component->orig_power_enable_fun_(enable); } -bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; } +bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 2dbd8ccd9d..326cd1edea 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -26,6 +26,12 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; +enum class EthernetComponentState { + STOPPED, + CONNECTING, + CONNECTED, +}; + class EthernetComponent : public Component { public: EthernetComponent(); @@ -65,9 +71,9 @@ class EthernetComponent : public Component { eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN}; optional manual_ip_{}; - bool initialized_{false}; + bool started_{false}; bool connected_{false}; - bool last_connected_{false}; + EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; eth_config_t eth_config; eth_phy_power_enable_func orig_power_enable_fun_; From 4f9a56c8841a247f7d82fcd0cbe40902a77ba9e2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:30:04 +0200 Subject: [PATCH 046/285] Refactor docker build system and workflows (#2023) --- .github/workflows/ci-docker.yml | 47 ++-- .github/workflows/ci.yml | 178 ++++++------- .github/workflows/docker-lint-build.yml | 108 ++++++-- .github/workflows/matchers/pytest.json | 19 ++ .github/workflows/release-dev.yml | 247 ------------------- .github/workflows/release.yml | 315 ++++++------------------ docker/Dockerfile | 2 +- docker/Dockerfile.hassio | 2 +- docker/Dockerfile.lint | 3 +- docker/build.py | 177 +++++++++++++ esphome/const.py | 6 +- script/bump-docker-base-version.py | 50 ---- script/bump-version.py | 10 +- 13 files changed, 456 insertions(+), 708 deletions(-) create mode 100644 .github/workflows/matchers/pytest.json delete mode 100644 .github/workflows/release-dev.yml create mode 100755 docker/build.py delete mode 100755 script/bump-docker-base-version.py diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 91ec88aeb3..45fd3e141b 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -18,38 +18,23 @@ jobs: name: Build docker containers runs-on: ubuntu-latest strategy: - fail-fast: false matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v2 - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=ci" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121b3f1339..de777d5a3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,40 +4,36 @@ name: CI on: push: - # On dev branch release-dev already performs CI checks - # On other branches the `pull_request` trigger will be used - branches: [beta, release] + branches: [dev, beta, release] pull_request: jobs: - lint-clang-format: + ci-with-container: + name: ${{ matrix.name }} runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files strategy: fail-fast: false matrix: - split: [1, 2, 3, 4] + include: + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy 1/4 + split: 1 + - id: clang-tidy + name: Run script/clang-tidy 2/4 + split: 2 + - id: clang-tidy + name: Run script/clang-tidy 3/4 + split: 3 + - id: clang-tidy + name: Run script/clang-tidy 4/4 + split: 4 + + # cpp lint job runs with esphome-lint docker image so that clang-format-* + # doesn't have to be installed + container: esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled @@ -45,26 +41,57 @@ jobs: - name: Set up platformio environment run: pio init --ide atom - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + + - name: Run clang-format + run: script/clang-format -i + if: ${{ matrix.id == 'clang-format' }} + - name: Run clang-tidy run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + if: ${{ matrix.id == 'clang-tidy' }} + - name: Suggest changes run: script/ci-suggest-changes - lint-python: + ci: # Don't use the esphome-lint docker image because it may contain outdated requirements. # This way, all dependencies are cached via the cache action. + name: ${{ matrix.name }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - id: ci-custom + name: Run script/ci-custom + - id: lint-python + name: Run script/lint-python + - id: test + file: tests/test1.yaml + name: Test tests/test1.yaml + - id: test + file: tests/test2.yaml + name: Test tests/test2.yaml + - id: test + file: tests/test3.yaml + name: Test tests/test3.yaml + - id: test + file: tests/test4.yaml + name: Test tests/test4.yaml + - id: pytest + name: Run pytest + steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Cache pip modules uses: actions/cache@v1 with: @@ -72,6 +99,17 @@ jobs: key: esphome-pip-3.7-${{ hashFiles('setup.py') }} restore-keys: | esphome-pip-3.7- + + # Use per test platformio cache because tests have different platform versions + - name: Cache ~/.platformio + uses: actions/cache@v1 + with: + path: ~/.platformio + key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} + restore-keys: | + test-home-platformio-${{ matrix.file }}- + if: ${{ matrix.id == 'test' }} + - name: Set up python environment run: script/setup @@ -80,82 +118,22 @@ jobs: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" echo "::add-matcher::.github/workflows/matchers/lint-python.json" echo "::add-matcher::.github/workflows/matchers/python.json" + echo "::add-matcher::.github/workflows/matchers/pytest.json" + echo "::add-matcher::.github/workflows/matchers/gcc.json" + - name: Lint Custom - run: script/ci-custom.py + run: | + script/ci-custom.py + script/build_codeowners.py --check + if: ${{ matrix.id == 'ci-custom' }} - name: Lint Python run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check + if: ${{ matrix.id == 'lint-python' }} - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup + - run: esphome compile ${{ matrix.file }} + if: ${{ matrix.id == 'test' }} - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - name: Run pytest run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + pytest -vv --tb=native tests + if: ${{ matrix.id == 'pytest' }} diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index d254ac332a..32aec87cdd 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -13,30 +13,88 @@ on: - '.github/workflows/docker-lint-build.yml' jobs: - publish-docker-lint-iage: - name: Build docker containers + deploy-docker: + name: Build and publish docker containers + if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, armv7, aarch64] + build_type: ["lint"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - echo "TAG=1.1" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "esphome/esphome-lint:latest" || true - - name: Build - run: | - docker build \ - --cache-from "esphome/esphome-lint:latest" \ - --file "docker/Dockerfile.lint" \ - --tag "esphome/esphome-lint:latest" \ - --tag "esphome/esphome-lint:${TAG}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "esphome/esphome-lint:${TAG}" - docker push "esphome/esphome-lint:latest" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run push + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push + + deploy-docker-manifest: + if: github.repository == 'esphome/esphome' + runs-on: ubuntu-latest + needs: [deploy-docker] + strategy: + matrix: + build_type: ["lint"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run manifest + run: | + docker/build.py \ + --tag "${TAG}" \ + --build-type "${{ matrix.build_type }}" \ + manifest diff --git a/.github/workflows/matchers/pytest.json b/.github/workflows/matchers/pytest.json new file mode 100644 index 0000000000..0eb8f050e6 --- /dev/null +++ b/.github/workflows/matchers/pytest.json @@ -0,0 +1,19 @@ +{ + "problemMatcher": [ + { + "owner": "pytest", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$", + "file": 1, + "line": 2 + }, + { + "regexp": "^\\s+(.*)$", + "message": 1 + } + ] + } + ] +} diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml deleted file mode 100644 index f8b90d524f..0000000000 --- a/.github/workflows/release-dev.yml +++ /dev/null @@ -1,247 +0,0 @@ -name: Publish dev releases to docker hub - -on: - push: - branches: - - dev - -jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests - - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] - strategy: - matrix: - arch: [amd64, armv7, aarch64] - # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly - build_type: ["docker"] - steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" - - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --tag "${BUILD_TO}:dev" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "${BUILD_TO}:${TAG}" - docker push "${BUILD_TO}:dev" - - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - steps: - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} - - docker manifest create esphome/esphome:dev \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 383f2878e5..1b15b540f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,164 +1,35 @@ name: Publish Release on: + workflow_dispatch: release: types: [published] + schedule: + - cron: "0 2 * * *" jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: + init: + name: Initialize build runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 + outputs: + tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers + - name: Get tag + id: tag run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") + today="$(date --utc '+%Y%m%d')" + TAG="${TAG}${today}" + fi + echo "::set-output name=tag::${TAG}" deploy-pypi: name: Build and publish to PyPi - if: github.repository == 'esphome/esphome' - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -182,119 +53,85 @@ jobs: name: Build and publish docker containers if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + needs: [init] strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi + - name: Run build + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build - if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then - cache_tag="beta" - else - cache_tag="latest" - fi + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - # Set env variables so these values don't need to be calculated again - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:${CACHE_TAG}" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --cache-from "${BUILD_TO}:${CACHE_TAG}" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: docker push "${BUILD_TO}:${TAG}" - - # Always publish to beta tag (also full releases) - - name: Publish docker beta tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta" - docker push "${BUILD_TO}:beta" - - - if: ${{ !github.event.release.prerelease }} - name: Publish docker latest tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest" - docker push "${BUILD_TO}:latest" + - name: Run push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push deploy-docker-manifest: if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [deploy-docker] + needs: [init, deploy-docker] + strategy: + matrix: + build_type: ["ha-addon", "docker"] steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Enable experimental manifest support run: | mkdir -p ~/.docker echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV + - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish docker beta tag + - name: Run manifest run: | - docker manifest create esphome/esphome:beta \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:beta - - - name: Publish docker latest tag - if: ${{ !github.event.release.prerelease }} - run: | - docker manifest create esphome/esphome:latest \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:latest + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-hassio-repo: - if: github.repository == 'esphome/esphome' + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] steps: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d126a2944..907c041119 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0 +ARG BUILD_FROM=esphome/esphome-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 5dd9339b18..ad80074ada 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM +ARG BUILD_FROM=esphome/esphome-hassio-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint index 60d63152a0..3a090c3b41 100644 --- a/docker/Dockerfile.lint +++ b/docker/Dockerfile.lint @@ -1,4 +1,5 @@ -FROM esphome/esphome-lint-base:3.4.0 +ARG BUILD_FROM=esphome/esphome-lint-base:latest +FROM ${BUILD_FROM} COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / RUN \ diff --git a/docker/build.py b/docker/build.py new file mode 100755 index 0000000000..97222df8d1 --- /dev/null +++ b/docker/build.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +import subprocess +import argparse +import platform +import shlex +import re +import sys + + +CHANNEL_DEV = 'dev' +CHANNEL_BETA = 'beta' +CHANNEL_RELEASE = 'release' +CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE] + +ARCH_AMD64 = 'amd64' +ARCH_ARMV7 = 'armv7' +ARCH_AARCH64 = 'aarch64' +ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64] + +TYPE_DOCKER = 'docker' +TYPE_HA_ADDON = 'ha-addon' +TYPE_LINT = 'lint' +TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] + + +BASE_VERSION = "3.6.0" + + +parser = argparse.ArgumentParser() +parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") +parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") +parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run") +parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") +subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) +build_parser = subparsers.add_parser("build", help="Build the image") +push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") + + + +# only lists some possibilities, doesn't have to be perfect +# https://stackoverflow.com/a/45125525 +UNAME_TO_ARCH = { + "x86_64": ARCH_AMD64, + "aarch64": ARCH_AARCH64, + "aarch64_be": ARCH_AARCH64, + "arm": ARCH_ARMV7, +} + + +@dataclass(frozen=True) +class DockerParams: + build_from: str + build_to: str + manifest_to: str + dockerfile: str + + @classmethod + def for_type_arch(cls, build_type, arch): + prefix = { + TYPE_DOCKER: "esphome/esphome", + TYPE_HA_ADDON: "esphome/esphome-hassio", + TYPE_LINT: "esphome/esphome-lint" + }[build_type] + build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" + build_to = f"{prefix}-{arch}" + dockerfile = { + TYPE_DOCKER: "docker/Dockerfile", + TYPE_HA_ADDON: "docker/Dockerfile.hassio", + TYPE_LINT: "docker/Dockerfile.lint", + }[build_type] + return cls( + build_from=build_from, + build_to=build_to, + manifest_to=prefix, + dockerfile=dockerfile + ) + + +def main(): + args = parser.parse_args() + + def run_command(*cmd, ignore_error: bool = False): + print(f"$ {shlex.join(list(cmd))}") + if not args.dry_run: + rc = subprocess.call(list(cmd)) + if rc != 0 and not ignore_error: + print("Command failed") + sys.exit(1) + + # detect channel from tag + match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + if match is None: + channel = CHANNEL_DEV + elif match.group(1) is None: + channel = CHANNEL_RELEASE + else: + channel = CHANNEL_BETA + + tags_to_push = [args.tag] + if channel == CHANNEL_DEV: + tags_to_push.append("dev") + elif channel == CHANNEL_BETA: + tags_to_push.append("beta") + elif channel == CHANNEL_RELEASE: + # Additionally push to beta + tags_to_push.append("beta") + tags_to_push.append("latest") + + if args.command == "build": + # 1. pull cache image + params = DockerParams.for_type_arch(args.build_type, args.arch) + cache_tag = { + CHANNEL_DEV: "dev", + CHANNEL_BETA: "beta", + CHANNEL_RELEASE: "latest", + }[channel] + cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" + run_command("docker", "pull", cache_img, ignore_error=True) + + # 2. register QEMU binfmt (if not host arch) + is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch + if not is_native: + run_command( + "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", + "--reset", "-p", "yes" + ) + + # 3. build + run_command( + "docker", "build", + "--build-arg", f"BUILD_FROM={params.build_from}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--tag", f"{params.build_to}:{args.tag}", + "--cache-from", cache_img, + "--file", params.dockerfile, + "." + ) + elif args.command == "push": + params = DockerParams.for_type_arch(args.build_type, args.arch) + imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] + imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] + src = imgs[0] + # 1. tag images + for img in imgs[1:]: + run_command( + "docker", "tag", src, img + ) + # 2. push images + for img in imgs: + run_command( + "docker", "push", img + ) + elif args.command == "manifest": + manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to + + targets = [f"{manifest}:{tag}" for tag in tags_to_push] + targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push] + # 1. Create manifests + for target in targets: + cmd = ["docker", "manifest", "create", target] + for arch in ARCHS: + src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}" + if target.startswith("ghcr.io"): + src = f"ghcr.io/{src}" + cmd.append(src) + run_command(*cmd) + # 2. Push manifests + for target in targets: + run_command( + "docker", "manifest", "push", target + ) + + +if __name__ == "__main__": + main() diff --git a/esphome/const.py b/esphome/const.py index 35f1e560e1..c336f38b22 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,10 +1,6 @@ """Constants used by esphome.""" -MAJOR_VERSION = 1 -MINOR_VERSION = 20 -PATCH_VERSION = "0b2" -__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" -__version__ = f"{__short_version__}.{PATCH_VERSION}" +__version__ = "1.20.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py deleted file mode 100755 index f5f4399fd2..0000000000 --- a/script/bump-docker-base-version.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import sys - - -def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: - content = fh.read() - content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) - if expected_count is not None: - assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "wt") as fh: - fh.write(content) - - -def write_version(version: str): - for p in [ - ".github/workflows/ci-docker.yml", - ".github/workflows/release-dev.yml", - ".github/workflows/release.yml", - ]: - sub(p, r'base_version=".*"', f'base_version="{version}"') - - sub( - "docker/Dockerfile", - r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", - f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", - ) - sub( - "docker/Dockerfile.lint", - r"FROM esphome/esphome-lint-base:.*", - f"FROM esphome/esphome-lint-base:{version}", - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("new_version", type=str) - args = parser.parse_args() - - version = args.new_version - print(f"Bumping to {version}") - write_version(version) - return 0 - - -if __name__ == "__main__": - sys.exit(main() or 0) diff --git a/script/bump-version.py b/script/bump-version.py index b7b048eb22..1f034344f9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): - sub( - "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}" - ) - sub( - "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}" - ) sub( "esphome/const.py", - r"^PATCH_VERSION = .*$", - f'PATCH_VERSION = "{version.full_patch}"', + r"^__version__ = .*$", + f'__version__ = "{version}"', ) From 70902029f88e2faf2a9ed41140febdede08e1331 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:51:52 +0200 Subject: [PATCH 047/285] GH Actions CI use GHCR (#2027) --- .github/workflows/ci.yml | 2 +- docker/build.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de777d5a3b..4ccaaa47b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled diff --git a/docker/build.py b/docker/build.py index 97222df8d1..54a279f845 100755 --- a/docker/build.py +++ b/docker/build.py @@ -90,7 +90,7 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) if match is None: channel = CHANNEL_DEV elif match.group(1) is None: From 7787fa8f29cbc4c1753c4e5c30cbf386620689d4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:22:42 +0200 Subject: [PATCH 048/285] Dashboard disable assets caching (#2025) --- esphome/dashboard/dashboard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 00b12199c0..66f72bfc00 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)): class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): - if debug: - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) app_settings = { "debug": debug, From f1e3ff2ed2c799a702e8f518d9b78fe558680283 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:23:08 +0200 Subject: [PATCH 049/285] Improve external components error messages (#2026) --- .../external_components/__init__.py | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 1602ac3b07..bf44dc1929 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _handle_git_response(ret): +def _run_git_command(cmd): + try: + ret = subprocess.run(cmd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + if ret.returncode != 0 and ret.stderr: err_str = ret.stderr.decode("utf-8") lines = [x.strip() for x in err_str.splitlines()] @@ -118,46 +126,59 @@ def _handle_git_response(ret): raise cv.Invalid(err_str) +def _process_git_config(config: dict, refresh) -> str: + key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" + repo_dir = _compute_destination_path(key) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if CONF_REF in config: + cmd += ["--branch", config[CONF_REF]] + cmd += ["--", config[CONF_URL], str(repo_dir)] + _run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.datetime.now() - datetime.datetime.fromtimestamp( + file_timestamp.stat().st_mtime + ) + if age.seconds > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + _run_git_command(["git", "stash", "push", "--include-untracked"]) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if CONF_REF in config: + cmd.append(config[CONF_REF]) + _run_git_command(cmd) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + + if (repo_dir / "esphome" / "components").is_dir(): + components_dir = repo_dir / "esphome" / "components" + elif (repo_dir / "components").is_dir(): + components_dir = repo_dir / "components" + else: + raise cv.Invalid( + "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder" + ) + + return components_dir + + def _process_single_config(config: dict): conf = config[CONF_SOURCE] if conf[CONF_TYPE] == TYPE_GIT: - key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in conf: - cmd += ["--branch", conf[CONF_REF]] - cmd += [conf[CONF_URL], str(repo_dir)] - ret = subprocess.run(cmd, capture_output=True, check=False) - _handle_git_response(ret) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime + with cv.prepend_path([CONF_SOURCE]): + components_dir = _process_git_config( + config[CONF_SOURCE], config[CONF_REFRESH] ) - if age.seconds > config[CONF_REFRESH].total_seconds: - _LOGGER.info("Executing git pull %s", key) - cmd = ["git", "pull"] - ret = subprocess.run( - cmd, cwd=repo_dir, capture_output=True, check=False - ) - _handle_git_response(ret) - - if (repo_dir / "esphome" / "components").is_dir(): - components_dir = repo_dir / "esphome" / "components" - elif (repo_dir / "components").is_dir(): - components_dir = repo_dir / "components" - else: - raise cv.Invalid( - "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder", - [CONF_SOURCE], - ) - elif conf[CONF_TYPE] == TYPE_LOCAL: components_dir = Path(CORE.relative_config_path(conf[CONF_PATH])) else: From 46e50ba53ff99fdd83fd1997c12c8adcd9e884e9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Jul 2021 08:28:58 +1200 Subject: [PATCH 050/285] Bump version to v1.20.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c336f38b22..65c6458dd6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b2" +__version__ = "1.20.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 98855e4123f236530509f7f40c3854d538375e3e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 08:22:49 +1200 Subject: [PATCH 051/285] Number and Template Number updates (#2036) Co-authored-by: Otto winter --- esphome/components/api/api_connection.cpp | 8 +- esphome/components/mqtt/mqtt_number.cpp | 22 +++-- esphome/components/number/__init__.py | 27 ++++-- esphome/components/number/number.cpp | 69 ++++---------- esphome/components/number/number.h | 93 +++++++------------ .../components/template/number/__init__.py | 25 +++-- .../template/number/template_number.cpp | 33 ++++--- .../template/number/template_number.h | 19 ++-- esphome/components/web_server/web_server.cpp | 5 +- 9 files changed, 148 insertions(+), 153 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8c76583fc7..79ffcfa69e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -570,11 +570,11 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->get_icon(); + msg.icon = number->traits.get_icon(); - msg.min_value = number->get_min_value(); - msg.max_value = number->get_max_value(); - msg.step = number->get_step(); + msg.min_value = number->traits.get_min_value(); + msg.max_value = number->traits.get_max_value(); + msg.step = number->traits.get_step(); return this->send_list_entities_number_response(msg); } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bb67a225fd..0311526340 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -3,10 +3,6 @@ #ifdef USE_NUMBER -#ifdef USE_DEEP_SLEEP -#include "esphome/components/deep_sleep/deep_sleep_component.h" -#endif - namespace esphome { namespace mqtt { @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { auto val = parse_float(state); if (!val.has_value()) { - ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); + ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; } auto call = this->number_->make_call(); @@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->number_->get_icon().empty()) - root["icon"] = this->number_->get_icon(); + const auto &traits = number_->traits; + // https://www.home-assistant.io/integrations/number.mqtt/ + if (!traits.get_icon().empty()) + root["icon"] = traits.get_icon(); + root["min_value"] = traits.get_min_value(); + root["max_value"] = traits.get_max_value(); + root["step"] = traits.get_step(); + + config.command_topic = true; } bool MQTTNumberComponent::send_initial_state() { if (this->number_->has_state()) { @@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() { } bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { - int8_t accuracy = this->number_->get_accuracy_decimals(); - return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + return this->publish(this->get_state_topic_(), buffer); } } // namespace mqtt diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ed33931d8b..bf95cb1b31 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -1,3 +1,4 @@ +from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( ) -async def setup_number_core_(var, config): +async def setup_number_core_( + var, config, *, min_value: float, max_value: float, step: Optional[float] +): cg.add(var.set_name(config[CONF_NAME])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) - cg.add(var.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_min_value(min_value)) + cg.add(var.traits.set_max_value(max_value)) + if step is not None: + cg.add(var.traits.set_step(step)) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -92,16 +99,24 @@ async def setup_number_core_(var, config): await mqtt.register_mqtt_component(mqtt_, config) -async def register_number(var, config): +async def register_number( + var, config, *, min_value: float, max_value: float, step: Optional[float] = None +): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_number(var)) - await setup_number_core_(var, config) + await setup_number_core_( + var, config, min_value=min_value, max_value=max_value, step=step + ) -async def new_number(config): +async def new_number( + config, *, min_value: float, max_value: float, step: Optional[float] = None +): var = cg.new_Pvariable(config[CONF_ID]) - await register_number(var, config) + await register_number( + var, config, min_value=min_value, max_value=max_value, step=step + ) return var diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index eaee5d4e69..dbc1c88a5d 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,67 +8,38 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (this->value_.has_value()) { - auto value = *this->value_; - uint8_t accuracy = this->parent_->get_accuracy_decimals(); - float min_value = this->parent_->get_min_value(); - if (value < min_value) { - ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(min_value, accuracy).c_str()); - this->value_.reset(); - return; - } - float max_value = this->parent_->get_max_value(); - if (value > max_value) { - ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(max_value, accuracy).c_str()); - this->value_.reset(); - return; - } - ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); - this->parent_->set(*this->value_); + if (!this->value_.has_value() || isnan(*this->value_)) { + ESP_LOGW(TAG, "No value set for NumberCall"); + return; } + + const auto &traits = this->parent_->traits; + auto value = *this->value_; + + float min_value = traits.get_min_value(); + if (value < min_value) { + ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value); + return; + } + float max_value = traits.get_max_value(); + if (value > max_value) { + ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value); + return; + } + ESP_LOGD(TAG, " Value: %f", *this->value_); + this->parent_->control(*this->value_); } -NumberCall &NumberCall::set_value(float value) { - this->value_ = value; - return *this; -} - -const optional &NumberCall::get_value() const { return this->value_; } - -NumberCall Number::make_call() { return NumberCall(this); } - void Number::publish_state(float state) { this->has_state_ = true; this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); + ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); } -uint32_t Number::update_interval() { return 0; } -Number::Number(const std::string &name) : Nameable(name), state(NAN) {} -Number::Number() : Number("") {} - void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void Number::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Number::get_icon() { return *this->icon_; } -int8_t Number::get_accuracy_decimals() { - // use printf %g to find number of digits based on step - char buf[32]; - sprintf(buf, "%.5g", this->step_); - std::string str{buf}; - size_t dot_pos = str.find('.'); - if (dot_pos == std::string::npos) - return 0; - - return str.length() - dot_pos - 1; -} -float Number::get_state() const { return this->state; } - -bool Number::has_state() const { return this->has_state_; } uint32_t Number::hash_base() { return 2282307003UL; } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 4fe9692a6b..e32b53187b 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -9,8 +9,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + if (!(obj)->traits.get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ } @@ -19,72 +19,56 @@ class Number; class NumberCall { public: explicit NumberCall(Number *parent) : parent_(parent) {} - NumberCall &set_value(float value); void perform(); - const optional &get_value() const; + NumberCall &set_value(float value) { + value_ = value; + return *this; + } + const optional &get_value() const { return value_; } protected: Number *const parent_; optional value_; }; +class NumberTraits { + public: + void set_min_value(float min_value) { min_value_ = min_value; } + float get_min_value() const { return min_value_; } + void set_max_value(float max_value) { max_value_ = max_value; } + float get_max_value() const { return max_value_; } + void set_step(float step) { step_ = step; } + float get_step() const { return step_; } + void set_icon(std::string icon) { icon_ = std::move(icon); } + const std::string &get_icon() const { return icon_; } + + protected: + float min_value_ = NAN; + float max_value_ = NAN; + float step_ = NAN; + std::string icon_; +}; + /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ class Number : public Nameable { public: - explicit Number(); - explicit Number(const std::string &name); - - /** Manually set the icon of this number. By default the number's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ - void set_icon(const std::string &icon); - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - - /// Getter-syntax for .state. - float get_state() const; - - /// Get the accuracy in decimals. Based on the step value. - int8_t get_accuracy_decimals(); - - /** Publish the current state to the front-end. - */ - void publish_state(float state); - - NumberCall make_call(); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Add a callback that will be called every time the state changes. - void add_on_state_callback(std::function &&callback); - - /** This member variable stores the last state. - * - * On startup, when no state is available yet, this is NAN (not-a-number) and the validity - * can be checked using has_state(). - * - * This is exposed through a member variable for ease of use in esphome lambdas. - */ float state; + void publish_state(float state); + + NumberCall make_call() { return NumberCall(this); } + void set(float value) { make_call().set_value(value).perform(); } + + void add_on_state_callback(std::function &&callback); + + NumberTraits traits; + /// Return whether this number has gotten a full state yet. - bool has_state() const; - - /// Return with which interval the number is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - void set_min_value(float min_value) { this->min_value_ = min_value; } - void set_max_value(float max_value) { this->max_value_ = max_value; } - void set_step(float step) { this->step_ = step; } - - float get_min_value() const { return this->min_value_; } - float get_max_value() const { return this->max_value_; } - float get_step() const { return this->step_; } + bool has_state() const { return has_state_; } protected: friend class NumberCall; @@ -95,17 +79,12 @@ class Number : public Nameable { * * @param value The value as validated by the NumberCall. */ - virtual void set(float value) = 0; + virtual void control(float value) = 0; uint32_t hash_base() override; CallbackManager state_callback_; - /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. - optional icon_; bool has_state_{false}; - float step_{1.0}; - float min_value_{0}; - float max_value_{100}; }; } // namespace number diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index cf70a48c4d..557a01c6fa 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome.components import number from esphome.const import ( CONF_ID, + CONF_INITIAL_VALUE, CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, + cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_VALUE): cv.float_, } ).extend(cv.polling_component_schema("60s")), validate_min_max, @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await number.register_number(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], [], return_type=cg.optional.template(float) ) cg.add(var.set_template(template_)) + + elif CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - - cg.add(var.set_min_value(config[CONF_MIN_VALUE])) - cg.add(var.set_max_value(config[CONF_MAX_VALUE])) - cg.add(var.set_step(config[CONF_STEP])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 69c5d62684..500f9f2272 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -6,34 +6,45 @@ namespace template_ { static const char *const TAG = "template.number"; -TemplateNumber::TemplateNumber() : set_trigger_(new Trigger()) {} +void TemplateNumber::setup() { + if (this->f_.has_value() || !this->optimistic_) + return; + + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + float value; + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } + this->publish_state(value); +} void TemplateNumber::update() { if (!this->f_.has_value()) return; auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); - } + if (!val.has_value()) + return; + + this->publish_state(*val); } -void TemplateNumber::set(float value) { +void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) + if (this->optimistic_) { this->publish_state(value); + this->pref_.save(&value); + } } -float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateNumber::set_template(std::function()> &&f) { this->f_ = f; } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); LOG_UPDATE_INTERVAL(this); } -void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } -Trigger *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; - } // namespace template_ } // namespace esphome diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 4c633e3b53..50cd256b7f 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -3,27 +3,32 @@ #include "esphome/components/number/number.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/preferences.h" namespace esphome { namespace template_ { class TemplateNumber : public number::Number, public PollingComponent { public: - TemplateNumber(); - void set_template(std::function()> &&f); + void set_template(std::function()> &&f) { this->f_ = f; } + void setup() override; void update() override; void dump_config() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const; - void set_optimistic(bool optimistic); + Trigger *get_set_trigger() const { return set_trigger_; } + void set_optimistic(bool optimistic) { optimistic_ = optimistic; } + void set_initial_value(float initial_value) { initial_value_ = initial_value; } protected: - void set(float value) override; + void control(float value) override; bool optimistic_{false}; - Trigger *set_trigger_; + float initial_value_{NAN}; + Trigger *set_trigger_ = new Trigger(); optional()>> f_; + + ESPPreferenceObject pref_; }; } // namespace template_ diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 57eef7a946..b775d44211 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject &root) { root["id"] = "number-" + obj->get_object_id(); - std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); - root["state"] = state; + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + root["state"] = buffer; root["value"] = value; }); } From 0a82e6e792477c579ae60f19ab593ec8fcd47369 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 10:28:23 +1200 Subject: [PATCH 052/285] Bump version to v1.20.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 65c6458dd6..7f4f08274b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b3" +__version__ = "1.20.0b4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From c7a52c3894db640d4b5e793e542443d85766de1f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 15:40:42 +1200 Subject: [PATCH 053/285] Add restore_value to template number (#2041) --- .../components/template/number/__init__.py | 30 ++++++++++++++----- .../template/number/template_number.cpp | 23 ++++++++------ .../template/number/template_number.h | 2 ++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 557a01c6fa..22bbaacc15 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, CONF_STEP, ) from .. import template_ns @@ -26,6 +27,17 @@ def validate_min_max(config): return config +def validate(config): + if CONF_LAMBDA in config: + if CONF_OPTIMISTIC in config: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + return config + + CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend( { @@ -33,13 +45,15 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, - cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_min_max, + validate, ) @@ -60,13 +74,15 @@ async def to_code(config): ) cg.add(var.set_template(template_)) - elif CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + else: + if CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 500f9f2272..eb9b17b976 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -7,16 +7,20 @@ namespace template_ { static const char *const TAG = "template.number"; void TemplateNumber::setup() { - if (this->f_.has_value() || !this->optimistic_) + if (this->f_.has_value()) return; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); float value; - if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) - value = this->initial_value_; - else - value = this->traits.get_min_value(); + if (!this->restore_value_) { + value = this->initial_value_; + } else { + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } } this->publish_state(value); } @@ -35,10 +39,11 @@ void TemplateNumber::update() { void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) { + if (this->optimistic_) this->publish_state(value); + + if (this->restore_value_) this->pref_.save(&value); - } } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 50cd256b7f..9a82e44339 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -20,11 +20,13 @@ class TemplateNumber : public number::Number, public PollingComponent { Trigger *get_set_trigger() const { return set_trigger_; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_initial_value(float initial_value) { initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: void control(float value) override; bool optimistic_{false}; float initial_value_{NAN}; + bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); optional()>> f_; From e25935ef213bdefedbe9b8358d9aaaab9ef6fa2c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Tue, 20 Jul 2021 08:26:07 +0400 Subject: [PATCH 054/285] midea_ac: Fix turbo mode. Preset BOOST. (#2029) --- esphome/components/midea_ac/midea_frame.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index a84161b4af..3777f6fd77 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame { void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; } - void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); } + bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } + void set_turbo_mode(bool state) { + this->set_bytemask_(18, 0x20, state); + this->set_bytemask_(20, 0x02, state); + } /* FREEZE PROTECTION */ bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } From 5c57b5137809770a5781f07495e44fe075ad2f1e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 19 Jul 2021 21:44:39 -0700 Subject: [PATCH 055/285] Bump dashboard to 20210719.0 (#2043) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3562f492d..ab54828194 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210623.0 +esphome-dashboard==20210719.0 From 73ead5f3281a7042a09499cf3ccee27062bda096 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 01:05:25 -0400 Subject: [PATCH 056/285] Correct ADS1115 handling of multiple sensors in continuous mode (#2016) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ads1115/ads1115.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index d33ac83813..8cac897a15 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -107,17 +107,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { } this->prev_config_ = config; - // about 1.6 ms with 860 samples per second + // about 1.2 ms with 860 samples per second delay(2); - uint32_t start = millis(); - while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Reading ADS1115 timed out"); - this->status_set_warning(); - return NAN; + // in continuous mode, conversion will always be running, rely on the delay + // to ensure conversion is taking place with the correct settings + // can we use the rdy pin to trigger when a conversion is done? + if (!this->continuous_mode_) { + uint32_t start = millis(); + while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { + if (millis() - start > 100) { + ESP_LOGW(TAG, "Reading ADS1115 timed out"); + this->status_set_warning(); + return NAN; + } + yield(); } - yield(); } } From e5afb1c4eaf8c6a9f8603ff780ce9c70b2beede4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 20 Jul 2021 07:05:56 +0200 Subject: [PATCH 057/285] ESP32 ADC use esp-idf (#2024) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/adc/adc_sensor.cpp | 52 +++++++++++++++++++++------ esphome/components/adc/adc_sensor.h | 8 +++-- esphome/components/adc/sensor.py | 8 ++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 960d9ed8e2..d6469ab785 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -11,7 +11,30 @@ namespace adc { static const char *const TAG = "adc"; #ifdef ARDUINO_ARCH_ESP32 -void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } + +inline adc1_channel_t gpio_to_adc1(uint8_t pin) { + switch (pin) { + case 36: + return ADC1_CHANNEL_0; + case 37: + return ADC1_CHANNEL_1; + case 38: + return ADC1_CHANNEL_2; + case 39: + return ADC1_CHANNEL_3; + case 32: + return ADC1_CHANNEL_4; + case 33: + return ADC1_CHANNEL_5; + case 34: + return ADC1_CHANNEL_6; + case 35: + return ADC1_CHANNEL_7; + default: + return ADC1_CHANNEL_MAX; + } +} #endif void ADCSensor::setup() { @@ -21,7 +44,9 @@ void ADCSensor::setup() { #endif #ifdef ARDUINO_ARCH_ESP32 - analogSetPinAttenuation(this->pin_, this->attenuation_); + adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); + adc1_config_width(ADC_WIDTH_BIT_12); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); #endif } void ADCSensor::dump_config() { @@ -36,18 +61,20 @@ void ADCSensor::dump_config() { #ifdef ARDUINO_ARCH_ESP32 ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); break; - case ADC_6db: + case ADC_ATTEN_DB_6: ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); break; - case ADC_11db: + case ADC_ATTEN_DB_11: ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } #endif LOG_UPDATE_INTERVAL(this); @@ -60,20 +87,23 @@ void ADCSensor::update() { } float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP32 - float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT + int raw = adc1_get_raw(gpio_to_adc1(pin_)); + float value_v = raw / 4095.0f; switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: value_v *= 1.1; break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: value_v *= 1.5; break; - case ADC_6db: + case ADC_ATTEN_DB_6: value_v *= 2.2; break; - case ADC_11db: + case ADC_ATTEN_DB_11: value_v *= 3.9; break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } return value_v; #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 3a08ff6be4..4591ed758d 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -6,6 +6,10 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/adc.h" +#endif + namespace esphome { namespace adc { @@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef ARDUINO_ARCH_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_attenuation_t attenuation); + void set_attenuation(adc_atten_t attenuation); #endif /// Update adc values. @@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage uint8_t pin_; #ifdef ARDUINO_ARCH_ESP32 - adc_attenuation_t attenuation_{ADC_0db}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 90561679b7..7a944a7260 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -16,10 +16,10 @@ from esphome.const import ( AUTO_LOAD = ["voltage_sampler"] ATTENUATION_MODES = { - "0db": cg.global_ns.ADC_0db, - "2.5db": cg.global_ns.ADC_2_5db, - "6db": cg.global_ns.ADC_6db, - "11db": cg.global_ns.ADC_11db, + "0db": cg.global_ns.ADC_ATTEN_DB_0, + "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, + "6db": cg.global_ns.ADC_ATTEN_DB_6, + "11db": cg.global_ns.ADC_ATTEN_DB_11, } From fa72990a633025f612aef937e72877904141e42d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:09:58 +1200 Subject: [PATCH 058/285] Bump version to v1.20.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7f4f08274b..556c8c70fc 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b4" +__version__ = "1.20.0b5" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 35b5c1ed5600c4dc814bc328d24b33799d4d4b04 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 20 Jul 2021 22:42:03 +0200 Subject: [PATCH 059/285] Fix white value transition for addressable lights (#2045) --- esphome/components/light/addressable_light.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ea24736c63..4ef293cd03 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) { // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); - uint8_t orig_w = target_color.w; target_color *= static_cast(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f)); - // w is not scaled by brightness - target_color.w = orig_w; float denom = (1.0f - new_smoothed); float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; From e19aa3bbe0ca9d3cacd92cc011a8e01a553f6965 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 09:20:20 +1200 Subject: [PATCH 060/285] Adding last_reset_type to sensors that should support it. (#2039) --- esphome/components/api/api.proto | 7 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 ++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/atm90e32/sensor.py | 15 +++++++++++-- esphome/components/havells_solar/sensor.py | 7 ++++-- esphome/components/hlw8012/sensor.py | 9 ++++++-- esphome/components/pzem004t/sensor.py | 5 +++-- esphome/components/pzemac/sensor.py | 5 +++-- esphome/components/sdm_meter/sensor.py | 22 ++++++++++++++----- esphome/components/sensor/__init__.py | 25 ++++++++++++++++++++++ esphome/components/sensor/sensor.cpp | 13 +++++++++++ esphome/components/sensor/sensor.h | 24 +++++++++++++++++++++ esphome/const.py | 8 +++++++ 14 files changed, 153 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 40be1fd0db..073775ed2e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -422,6 +422,12 @@ enum SensorStateClass { STATE_CLASS_MEASUREMENT = 1; } +enum SensorLastResetType { + LAST_RESET_NONE = 0; + LAST_RESET_NEVER = 1; + LAST_RESET_AUTO = 2; +} + message ListEntitiesSensorResponse { option (id) = 16; option (source) = SOURCE_SERVER; @@ -438,6 +444,7 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; + SensorLastResetType last_reset_type = 11; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79ffcfa69e..fb05772e5e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); + msg.last_reset_type = static_cast(sensor->last_reset_type); return this->send_list_entities_sensor_response(msg); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c3cfc8cd76..057d71324f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string(enums::Sens return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::SensorLastResetType value) { + switch (value) { + case enums::LAST_RESET_NONE: + return "LAST_RESET_NONE"; + case enums::LAST_RESET_NEVER: + return "LAST_RESET_NEVER"; + case enums::LAST_RESET_AUTO: + return "LAST_RESET_AUTO"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LogLevel value) { switch (value) { case enums::LOG_LEVEL_NONE: @@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->state_class = value.as_enum(); return true; } + case 11: { + this->last_reset_type = value.as_enum(); + return true; + } default: return false; } @@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); + buffer.encode_enum(11, this->last_reset_type); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" state_class: "); out.append(proto_enum_to_string(this->state_class)); out.append("\n"); + + out.append(" last_reset_type: "); + out.append(proto_enum_to_string(this->last_reset_type)); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e3bb1d9106..0551508b4b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, }; +enum SensorLastResetType : uint32_t { + LAST_RESET_NONE = 0, + LAST_RESET_NEVER = 1, + LAST_RESET_AUTO = 2, +}; enum LogLevel : uint32_t { LOG_LEVEL_NONE = 0, LOG_LEVEL_ERROR = 1, @@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; + enums::SensorLastResetType last_reset_type{}; void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 68ec199bff..2c34d76b52 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( ICON_EMPTY, ICON_LIGHTBULB, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_HERTZ, UNIT_VOLT, @@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 7d1e2be581..1926d4d68a 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_AMPERE, @@ -121,14 +122,16 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( UNIT_KILOWATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( UNIT_HOURS, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index e24e995eba..face32872e 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 1, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index e3859f090c..b358b8c650 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -12,8 +12,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -47,7 +47,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 778c5054a0..1dd77a0371 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -17,8 +17,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, ICON_EMPTY, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -54,7 +54,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 39ef280fef..ce560b9d4b 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -25,8 +25,8 @@ from esphome.const import ( ICON_CURRENT_AC, ICON_EMPTY, ICON_FLASH, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_DEGREES, UNIT_EMPTY, @@ -88,24 +88,36 @@ CONFIG_SCHEMA = ( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 89bde9476a..0a0c3a9214 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, + CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -30,6 +31,9 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, + LAST_RESET_TYPE_AUTO, + LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_NONE, UNIT_EMPTY, ICON_EMPTY, DEVICE_CLASS_EMPTY, @@ -79,6 +83,15 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") +LastResetTypes = sensor_ns.enum("LastResetType") +LAST_RESET_TYPES = { + LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, + LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, +} +validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") + + IS_PLATFORM_COMPONENT = True @@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, + cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -202,6 +216,7 @@ def sensor_schema( accuracy_decimals_: int, device_class_: Optional[str] = DEVICE_CLASS_EMPTY, state_class_: Optional[str] = STATE_CLASS_NONE, + last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement_ != UNIT_EMPTY: @@ -230,6 +245,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class} ) + if last_reset_type_ != LAST_RESET_TYPE_NONE: + schema = schema.extend( + { + cv.Optional( + CONF_LAST_RESET_TYPE, default=last_reset_type_ + ): validate_last_reset_type + } + ) return schema @@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + if CONF_LAST_RESET_TYPE in config: + cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fe92f88308..6e8765a8df 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) { } } +const char *last_reset_type_to_string(LastResetType last_reset_type) { + switch (last_reset_type) { + case LAST_RESET_TYPE_NEVER: + return "never"; + case LAST_RESET_TYPE_AUTO: + return "auto"; + case LAST_RESET_TYPE_NONE: + default: + return ""; + } +} + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } +void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 123e7eddb3..b9908b6cbe 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,6 +14,10 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ + (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ + ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ + } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,6 +41,20 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); +/** + * Sensor last reset types + */ +enum LastResetType : uint8_t { + /// This sensor does not support resetting. ie, it is not accumulative + LAST_RESET_TYPE_NONE = 0, + /// This sensor is expected to never reset its value + LAST_RESET_TYPE_NEVER = 1, + /// This sensor may reset and Home Assistant will watch for this + LAST_RESET_TYPE_AUTO = 2, +}; + +const char *last_reset_type_to_string(LastResetType last_reset_type); + /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -155,6 +173,12 @@ class Sensor : public Nameable { */ virtual std::string device_class(); + // The Last reset type of this sensor + LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; + + /// Manually set the Home Assistant last reset type for this sensor. + void set_last_reset_type(LastResetType last_reset_type); + /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/const.py b/esphome/const.py index 556c8c70fc..f85016aeb5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -297,6 +297,7 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" +CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -785,3 +786,10 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" + +# This sensor does not support resetting. ie, it is not accumulative +LAST_RESET_TYPE_NONE = "" +# This sensor is expected to never reset its value +LAST_RESET_TYPE_NEVER = "never" +# This sensor may reset and Home Assistant will watch for this +LAST_RESET_TYPE_AUTO = "auto" From 837930234fe0ba3df3d5f22d1b648d7ab9082a8f Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 17:35:45 -0400 Subject: [PATCH 061/285] Remove superfluous polling on ADS1115 (#2015) --- esphome/components/ads1115/ads1115.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 8cac897a15..92f0ffbe3a 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -64,11 +64,6 @@ void ADS1115Component::setup() { return; } this->prev_config_ = config; - - for (auto *sensor : this->sensors_) { - this->set_interval(sensor->get_name(), sensor->update_interval(), - [this, sensor] { this->request_measurement(sensor); }); - } } void ADS1115Component::dump_config() { ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); From 27112e2ace456eea883ee37d13d8ea970428908f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 10:52:48 +1200 Subject: [PATCH 062/285] Bump version to v1.20.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f85016aeb5..d8a4448d1f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b5" +__version__ = "1.20.0b6" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 4b8ec44262922e056c6ab84894d75c2e9575e293 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Jul 2021 07:55:49 +1200 Subject: [PATCH 063/285] Bump version to v1.20.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d8a4448d1f..48790cf16a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b6" +__version__ = "1.20.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From caa651e55badb77b3f3bac20346ef1384a473569 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 22 Jul 2021 14:37:42 +0200 Subject: [PATCH 064/285] Accept change as proposed by black. (#2055) --- esphome/components/external_components/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index bf44dc1929..3e833d0b66 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,9 +109,9 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _run_git_command(cmd): +def _run_git_command(cmd, cwd=None): try: - ret = subprocess.run(cmd, capture_output=True, check=False) + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) except FileNotFoundError as err: raise cv.Invalid( "git is not installed but required for external_components.\n" @@ -151,14 +151,16 @@ def _process_git_config(config: dict, refresh) -> str: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) - _run_git_command(["git", "stash", "push", "--include-untracked"]) + _run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) # Fetch remote ref cmd = ["git", "fetch", "--", "origin"] if CONF_REF in config: cmd.append(config[CONF_REF]) - _run_git_command(cmd) + _run_git_command(cmd, str(repo_dir)) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" From 1efabd27d88d1596d79a9b61fddc7a18a83bb7fc Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 22 Jul 2021 16:39:21 +0400 Subject: [PATCH 065/285] midea_ac: fix presets implementation (#2054) --- esphome/components/midea_ac/midea_climate.cpp | 4 ++-- esphome/components/midea_ac/midea_frame.cpp | 22 ++++++++++++------- esphome/components/midea_ac/midea_frame.h | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 9fe5df7de3..72f7d23404 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const { ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); } break; - case climate::CLIMATE_PRESET_HOME: + case climate::CLIMATE_PRESET_NONE: return true; default: break; @@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() { if (traits_swing_both_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); traits.set_supported_presets({ - climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_NONE, }); if (traits_preset_eco_) traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 5f09f4314f..c0a5ce4b55 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) { } optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) { + if (this->get_eco_mode()) return climate::CLIMATE_PRESET_ECO; - } else if (this->get_sleep_mode()) { + if (this->get_sleep_mode()) return climate::CLIMATE_PRESET_SLEEP; - } else if (this->get_turbo_mode()) { + if (this->get_turbo_mode()) return climate::CLIMATE_PRESET_BOOST; - } else { - return climate::CLIMATE_PRESET_HOME; - } + return climate::CLIMATE_PRESET_NONE; } void PropertiesFrame::set_preset(climate::ClimatePreset preset) { + this->clear_presets(); switch (preset) { case climate::CLIMATE_PRESET_ECO: this->set_eco_mode(true); @@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) { } } +void PropertiesFrame::clear_presets() { + this->set_eco_mode(false); + this->set_sleep_mode(false); + this->set_turbo_mode(false); + this->set_freeze_protection_mode(false); +} + bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; void PropertiesFrame::set_custom_preset(const std::string &preset) { - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) { + this->clear_presets(); + if (preset == MIDEA_FREEZE_PROTECTION_PRESET) this->set_freeze_protection_mode(true); - } } bool PropertiesFrame::is_custom_fan_mode() const { diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index 3777f6fd77..e1d6fed49d 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -115,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame { /* PRESET */ optional get_preset() const; void set_preset(climate::ClimatePreset preset); + void clear_presets(); bool is_custom_preset() const; const std::string &get_custom_preset() const; From fda8dd4ce35671b7a8fe8b4de6a335bd31f349d0 Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Thu, 22 Jul 2021 14:39:57 +0200 Subject: [PATCH 066/285] Fixes new auto mode COOL and HEAT after #1994 (#2053) --- esphome/components/pid/pid_climate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index dac4426698..ef8a4df962 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -35,8 +35,8 @@ void PIDClimate::control(const climate::ClimateCall &call) { if (call.get_target_temperature().has_value()) this->target_temperature = *call.get_target_temperature(); - // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) + // If switching to off mode, set output immediately + if (this->mode == climate::CLIMATE_MODE_OFF) this->handle_non_auto_mode_(); this->publish_state(); From 64a3aa7092e6470c57e432681628811997f65c9b Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 26 Jul 2021 18:41:54 +1000 Subject: [PATCH 067/285] Log warning about lack of support for Anova nano (#2063) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index c4b08ca6b5..63e0710936 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); + ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); break; } this->char_handle_ = chr->handle; From 1e2a9e8348eb01a40a21a5fc06cf34241ed451dd Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 26 Jul 2021 04:39:03 -0500 Subject: [PATCH 068/285] Couple more updates for the Tuya component (#2065) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 40 ++++++++++++++++++++++++++++---- esphome/components/tuya/tuya.h | 1 + 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index d9a0a9932a..916a550675 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -8,9 +8,10 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 50; +static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { - this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -117,7 +118,13 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - switch ((TuyaCommandType) command) { + TuyaCommandType command_type = (TuyaCommandType) command; + + if (this->expected_response_.has_value() && this->expected_response_ == command_type) { + this->expected_response_.reset(); + } + + switch (command_type) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); this->protocol_version_ = version; @@ -316,6 +323,25 @@ void Tuya::send_raw_command_(TuyaCommand command) { uint8_t version = 0; this->last_command_timestamp_ = millis(); + switch (command.cmd) { + case TuyaCommandType::HEARTBEAT: + this->expected_response_ = TuyaCommandType::HEARTBEAT; + break; + case TuyaCommandType::PRODUCT_QUERY: + this->expected_response_ = TuyaCommandType::PRODUCT_QUERY; + break; + case TuyaCommandType::CONF_QUERY: + this->expected_response_ = TuyaCommandType::CONF_QUERY; + break; + case TuyaCommandType::DATAPOINT_DELIVER: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + case TuyaCommandType::DATAPOINT_QUERY: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + default: + break; + } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); @@ -332,8 +358,14 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; + + if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { + this->expected_response_.reset(); + } + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && + !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } @@ -345,7 +377,7 @@ void Tuya::send_command_(const TuyaCommand &command) { } void Tuya::send_empty_command_(TuyaCommandType command) { - send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } void Tuya::send_wifi_status_() { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 4ca4f56366..68decf7e9e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -113,6 +113,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; std::vector command_queue_; + optional expected_response_{}; uint8_t wifi_status_ = -1; }; From 71ded24fced532004c726c9578021e1cd7b0b8f6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 21:46:13 +0200 Subject: [PATCH 069/285] Fix MQTT climate custom fan modes without regular ones (#2071) --- esphome/components/mqtt/mqtt_climate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index ab8354e66c..5809b6616c 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["act_t"] = this->get_action_state_topic(); } - if (traits.get_supports_fan_modes()) { + if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); // fan_mode_state_topic From 887081fd710c80c2aabe9954d19d3f25b19f6424 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:43:05 +1200 Subject: [PATCH 070/285] Bump version to v1.20.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 48790cf16a..a8e9e9cfc4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0" +__version__ = "1.20.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From bfac6607d16cf6f7d1b5a3874a1a0793cc916e33 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Tue, 27 Jul 2021 21:01:15 -0500 Subject: [PATCH 071/285] More Tuya MCU robustness (#2080) --- esphome/components/tuya/tuya.cpp | 11 +++++++++-- esphome/components/tuya/tuya.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 916a550675..325f12a58d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace tuya { static const char *const TAG = "tuya"; -static const int COMMAND_DELAY = 50; +static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { @@ -114,6 +114,8 @@ void Tuya::handle_char_(uint8_t c) { this->rx_message_.push_back(c); if (!this->validate_message_()) { this->rx_message_.clear(); + } else { + this->last_rx_char_timestamp_ = millis(); } } @@ -357,7 +359,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { } void Tuya::process_command_queue_() { - uint32_t delay = millis() - this->last_command_timestamp_; + uint32_t now = millis(); + uint32_t delay = now - this->last_command_timestamp_; + + if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) { + this->rx_message_.clear(); + } if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 68decf7e9e..7ce4be4315 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -107,6 +107,7 @@ class Tuya : public Component, public uart::UARTDevice { int gpio_status_ = -1; int gpio_reset_ = -1; uint32_t last_command_timestamp_ = 0; + uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; std::vector listeners_; std::vector datapoints_; From c2f9ed7c59ded423a25710cc5841fcfc948b7fa8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jul 2021 15:18:31 +1200 Subject: [PATCH 072/285] Bump esphome dashboard to 20210728.0 (#2081) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab54828194..561bf0f4d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210719.0 +esphome-dashboard==20210728.0 From be4c71885932266aaff51b94f3127943ed5072b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 07:53:54 +1200 Subject: [PATCH 073/285] HLW8012 - Dump energy sensor config (#2082) --- esphome/components/hlw8012/hlw8012.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 79c25a45b0..ecdaa07ab2 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -45,6 +45,7 @@ void HLW8012Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_) LOG_SENSOR(" ", "Current", this->current_sensor_) LOG_SENSOR(" ", "Power", this->power_sensor_) + LOG_SENSOR(" ", "Energy", this->energy_sensor_) } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } void HLW8012Component::update() { From 62f3039d82c2996ee3e8f2e3a7d70e5a917adbdb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:46:15 +1200 Subject: [PATCH 074/285] Use sensor_schema for total_daily_energy (#2090) Co-authored-by: Otto Winter --- .../components/total_daily_energy/sensor.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index b70a0cece9..7fdd176d42 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -1,7 +1,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + DEVICE_CLASS_ENERGY, + ICON_EMPTY, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_EMPTY, +) DEPENDENCIES = ["time"] @@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_EMPTY, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(TotalDailyEnergy), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): From 456824669ff1bdfac7c0ec6fb4a0011c2683b407 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:51:17 +1200 Subject: [PATCH 075/285] Bump version to v1.20.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a8e9e9cfc4..1c1e9eb3a5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.1" +__version__ = "1.20.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 1d56f0b035961cc80d20162fe2cb9c08b77b8005 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:53:33 +1200 Subject: [PATCH 076/285] Set pulse meter total to use state class measurement and last reset type auto (#2097) --- esphome/components/pulse_meter/sensor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index e732971c3a..4b8e8a1be0 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, DEVICE_CLASS_EMPTY, @@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema( cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + UNIT_PULSES, + ICON_PULSE, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) From fee446c28a5b331477fa80df8e44641ff193a65a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 11:00:10 +1200 Subject: [PATCH 077/285] Bump version to v1.20.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 1c1e9eb3a5..d2cc495952 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.2" +__version__ = "1.20.3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 7828f48b9affbc3520bb2f8909a5950cdcfbd7ae Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:20:02 +1000 Subject: [PATCH 078/285] Correctly invert esp32 RMT TX (#2022) --- esphome/components/remote_transmitter/remote_transmitter.h | 1 + .../remote_transmitter/remote_transmitter_esp32.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 000fbabfee..853b5b6289 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, bool initialized_{false}; std::vector rmt_temp_; esp_err_t error_code_{ESP_OK}; + bool inverted_{false}; #endif uint8_t carrier_duty_percent_{50}; }; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 3d3e26160a..7b366fa52b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() { } else { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; + this->inverted_ = true; } esp_err_t error = rmt_config(&c); @@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val -= item; if (rmt_i % 2 == 0) { - rmt_item.level0 = static_cast(level); + rmt_item.level0 = static_cast(level ^ this->inverted_); rmt_item.duration0 = static_cast(item); } else { - rmt_item.level1 = static_cast(level); + rmt_item.level1 = static_cast(level ^ this->inverted_); rmt_item.duration1 = static_cast(item); this->rmt_temp_.push_back(rmt_item); } From 790d6ef94cad7e69f00e2800f4018c3f8195e060 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:32:08 +1000 Subject: [PATCH 079/285] Move configure_rmt() into setup() (#2028) --- .../components/remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 7b366fa52b..90166d2741 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -9,7 +9,7 @@ namespace remote_transmitter { static const char *const TAG = "remote_transmitter"; -void RemoteTransmitterComponent::setup() {} +void RemoteTransmitterComponent::setup() { this->configure_rmt(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); From 3ffa59f0cd43becb7d1a5ad1954bec1ac706b217 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 15:19:50 +0200 Subject: [PATCH 080/285] Fix climate restore schema changed resulting in invalid restore (#2068) Co-authored-by: Stefan Agner --- esphome/components/climate/climate.cpp | 6 +++++- esphome/components/climate/climate.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 07347e4eee..8da2206f37 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +// Random 32bit value; If this changes existing restore preferences are invalidated +static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; + optional Climate::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = + global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index ed5c5069b7..690e81c250 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -120,6 +120,7 @@ class ClimateCall { }; /// Struct used to save the state of the climate device in restore memory. +/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries. struct ClimateDeviceRestoreState { ClimateMode mode; bool uses_custom_fan_mode{false}; From 29f0508dc21393b03a877a1439ee3d5b5d3426c6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 28 Jul 2021 21:23:41 +0200 Subject: [PATCH 081/285] Fix PID climate breaks when restoring old modes (#2086) --- esphome/components/pid/pid_climate.cpp | 13 ++----------- esphome/components/pid/pid_climate.h | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index ef8a4df962..4c7d92e26d 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -37,7 +37,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { // If switching to off mode, set output immediately if (this->mode == climate::CLIMATE_MODE_OFF) - this->handle_non_auto_mode_(); + this->write_output_(0.0f); this->publish_state(); } @@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) { } this->pid_computed_callback_.call(); } -void PIDClimate::handle_non_auto_mode_() { - // in non-auto mode, switch directly to appropriate action - // - OFF mode -> Output at 0% - if (this->mode == climate::CLIMATE_MODE_OFF) { - this->write_output_(0.0); - } else { - assert(false); - } -} void PIDClimate::update_pid_() { float value; if (isnan(this->current_temperature) || isnan(this->target_temperature)) { @@ -135,7 +126,7 @@ void PIDClimate::update_pid_() { } if (this->mode == climate::CLIMATE_MODE_OFF) { - this->handle_non_auto_mode_(); + this->write_output_(0.0); } else { this->write_output_(value); } diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index f11d768867..ff301386b6 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component { bool supports_heat_() const { return this->heat_output_ != nullptr; } void write_output_(float value); - void handle_non_auto_mode_(); /// The sensor used for getting the current temperature sensor::Sensor *sensor_; From 5ce923ea90d03fceb98e158dadcab5bd0c478b99 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Fri, 30 Jul 2021 01:55:26 -0300 Subject: [PATCH 082/285] fix diplay trigger missing base class (#2099) --- esphome/components/display/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 2dff00da03..947b09a258 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_( DisplayIsDisplayingPageCondition = display_ns.class_( "DisplayIsDisplayingPageCondition", automation.Condition ) -DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger") +DisplayOnPageChangeTrigger = display_ns.class_( + "DisplayOnPageChangeTrigger", automation.Trigger +) CONF_ON_PAGE_CHANGE = "on_page_change" From a6fac2b1750b05a62acb3985556de2df7a063535 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sat, 31 Jul 2021 23:20:10 +1200 Subject: [PATCH 083/285] Fix min/max keys in MQTT Number to match Home Assistant (#2102) --- esphome/components/mqtt/mqtt_number.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 0311526340..f209f4fe20 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -39,8 +39,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo // https://www.home-assistant.io/integrations/number.mqtt/ if (!traits.get_icon().empty()) root["icon"] = traits.get_icon(); - root["min_value"] = traits.get_min_value(); - root["max_value"] = traits.get_max_value(); + root["min"] = traits.get_min_value(); + root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); config.command_topic = true; From bdbd8134553baf231bd0395d477ec4a74d2c0c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=C5=9Aliwi=C5=84ski?= Date: Sat, 31 Jul 2021 14:37:48 +0200 Subject: [PATCH 084/285] Use proper schema for the analog pin shorthand (#2103) The wrong error message is displayed like: > GPIO17 (TOUT) is an analog-only pin on the ESP8266. in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`. --- esphome/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/pins.py b/esphome/pins.py index 6356ae9bd0..e314e3fc30 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1104,7 +1104,7 @@ def shorthand_input_pullup_pin(value): def shorthand_analog_pin(value): value = analog_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value}) + return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value}) def validate_has_interrupt(value): From b0d12aeea11f39153cc9b8c0f2b43da64a895c6f Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 2 Aug 2021 10:28:25 +0200 Subject: [PATCH 085/285] [duty_cycle] initialize two missing variables (#2088) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 1 + esphome/components/duty_cycle/duty_cycle_sensor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 8b7446b681..c989421948 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -13,6 +13,7 @@ void DutyCycleSensor::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); this->last_update_ = micros(); + this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); } diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 2205bec729..e168f20eff 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { protected: GPIOPin *pin_; - DutyCycleSensorStore store_; + DutyCycleSensorStore store_{}; uint32_t last_update_; }; From dd637582a43e0eb297887e36c25287a62db61679 Mon Sep 17 00:00:00 2001 From: brambo123 <52667932+brambo123@users.noreply.github.com> Date: Tue, 3 Aug 2021 19:56:23 +0200 Subject: [PATCH 086/285] Fix time.on_time triggering if time jumped back (#1806) Co-authored-by: Otto winter --- esphome/components/time/automation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 6d34459fea..84cc76a762 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -22,7 +22,10 @@ void CronTrigger::loop() { return; if (this->last_check_.has_value()) { - if (*this->last_check_ >= time) { + if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { + // We went back in time (a lot), probably caused by time synchronization + ESP_LOGW(TAG, "Time has jumped back!"); + } else if (*this->last_check_ >= time) { // already handled this one return; } From eeaba74553d8a5ed629fd5fc6be83ec232eefca4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 4 Aug 2021 17:33:17 +0200 Subject: [PATCH 087/285] Fix external components not refreshing with default or high refresh time (#2122) --- esphome/components/external_components/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 3e833d0b66..8f6e2bece4 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -147,7 +147,7 @@ def _process_git_config(config: dict, refresh) -> str: age = datetime.datetime.now() - datetime.datetime.fromtimestamp( file_timestamp.stat().st_mtime ) - if age.seconds > refresh.total_seconds: + if age.total_seconds() > refresh.total_seconds: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) From 532907219baaa3b856c501308a2cbbc7bb38568b Mon Sep 17 00:00:00 2001 From: Otto winter Date: Wed, 4 Aug 2021 17:46:10 +0200 Subject: [PATCH 088/285] Bump version to v1.20.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d2cc495952..6dda61cce2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.3" +__version__ = "1.20.4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 1771e673d2169ad05041b4c3000c376aa95d0b8c Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:14:42 +0100 Subject: [PATCH 089/285] Warn if underscore character is used in hostname (#2079) * Prevent underscore character being used in 'name'. * Restrict underscores in hostnames, not all names. * Use hostname validator for node name. * Allow underscore in hostname but warn once. * Add renaming instructions link to warning. * Point underscore warning to FAQ section Co-authored-by: Otto Winter Co-authored-by: Otto Winter --- esphome/config_validation.py | 13 +++++++++++-- esphome/core/config.py | 2 +- tests/unit_tests/test_config_validation.py | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 84452839ad..3aebca81b8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -891,11 +891,20 @@ def validate_bytes(value): def hostname(value): value = string(value) + warned_underscore = False if len(value) > 63: raise Invalid("Hostnames can only be 63 characters long") for c in value: - if not (c.isalnum() or c in "_-"): - raise Invalid("Hostname can only have alphanumeric characters and _ or -") + if not (c.isalnum() or c in "-_"): + raise Invalid("Hostname can only have alphanumeric characters and -") + if c in "_" and not warned_underscore: + _LOGGER.warning( + "'%s': Using the '_' (underscore) character in the hostname is discouraged " + "as it can cause problems with some DHCP and local name services. " + "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + value, + ) + warned_underscore = True return value diff --git a/esphome/core/config.py b/esphome/core/config.py index 97748d71d8..45b8809f1e 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -158,7 +158,7 @@ def valid_project_name(value: str): CONFIG_SCHEMA = cv.Schema( { - cv.Required(CONF_NAME): cv.valid_name, + cv.Required(CONF_NAME): cv.hostname, cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), cv.Required(CONF_BOARD): validate_board, cv.Optional(CONF_COMMENT): cv.string, diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 16cfb16e94..e34c7064fa 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,6 +40,28 @@ def test_valid_name__invalid(value): config_validation.valid_name(value) +@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar")) +def test_hostname__valid(value): + actual = config_validation.hostname(value) + + assert actual == value + + +@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar")) +def test_hostname__invalid(value): + with pytest.raises(Invalid): + config_validation.hostname(value) + + +def test_hostname__warning(caplog): + actual = config_validation.hostname("foo_bar") + assert actual == "foo_bar" + assert ( + "Using the '_' (underscore) character in the hostname is discouraged" + in caplog.text + ) + + @given(one_of(integers(), text())) def test_string__valid(value): actual = config_validation.string(value) From 6144ce1fe04d8d6c3e9664d303ed750e35e9bde5 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 10 Aug 2021 14:44:31 -0500 Subject: [PATCH 090/285] Break the Tuya set_datapoint_value method into separate methods per datapoint type (#2059) Co-authored-by: Chris Nussbaum Co-authored-by: Trevor North --- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/tuya/fan/tuya_fan.cpp | 8 +- esphome/components/tuya/light/tuya_light.cpp | 12 +-- .../components/tuya/switch/tuya_switch.cpp | 2 +- esphome/components/tuya/tuya.cpp | 85 +++++++++++++------ esphome/components/tuya/tuya.h | 12 ++- 6 files changed, 83 insertions(+), 42 deletions(-) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 5f214d3bfe..a4ce06476d 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -54,14 +54,14 @@ void TuyaClimate::control(const climate::ClimateCall &call) { if (call.get_mode().has_value()) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state)); - this->parent_->set_datapoint_value(*this->switch_id_, switch_state); + this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state); } if (call.get_target_temperature().has_value()) { const float target_temperature = *call.get_target_temperature(); ESP_LOGV(TAG, "Setting target temperature: %.1f", target_temperature); - this->parent_->set_datapoint_value(*this->target_temperature_id_, - (int) (target_temperature / this->target_temperature_multiplier_)); + this->parent_->set_integer_datapoint_value(*this->target_temperature_id_, + (int) (target_temperature / this->target_temperature_multiplier_)); } } diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index ed92713e52..8738b7f4a0 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -67,20 +67,20 @@ void TuyaFan::dump_config() { void TuyaFan::write_state() { if (this->switch_id_.has_value()) { ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); - this->parent_->set_datapoint_value(*this->switch_id_, this->fan_->state); + this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state); } if (this->oscillation_id_.has_value()) { ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); - this->parent_->set_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); } if (this->direction_id_.has_value()) { bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); - this->parent_->set_datapoint_value(*this->direction_id_, enable); + this->parent_->set_boolean_datapoint_value(*this->direction_id_, enable); } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + this->parent_->set_integer_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index e0ca026248..8ad78aacea 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -31,7 +31,7 @@ void TuyaLight::setup() { }); } if (min_value_datapoint_id_.has_value()) { - parent_->set_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); + parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_); } } @@ -66,9 +66,9 @@ void TuyaLight::write_state(light::LightState *state) { if (brightness == 0.0f) { // turning off, first try via switch (if exists), then dimmer if (switch_id_.has_value()) { - parent_->set_datapoint_value(*this->switch_id_, false); + parent_->set_boolean_datapoint_value(*this->switch_id_, false); } else if (dimmer_id_.has_value()) { - parent_->set_datapoint_value(*this->dimmer_id_, 0); + parent_->set_integer_datapoint_value(*this->dimmer_id_, 0); } return; } @@ -78,17 +78,17 @@ void TuyaLight::write_state(light::LightState *state) { static_cast(this->color_temperature_max_value_ * (state->current_values.get_color_temperature() - this->cold_white_temperature_) / (this->warm_white_temperature_ - this->cold_white_temperature_)); - parent_->set_datapoint_value(*this->color_temperature_id_, color_temp_int); + parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); } auto brightness_int = static_cast(brightness * this->max_value_); brightness_int = std::max(brightness_int, this->min_value_); if (this->dimmer_id_.has_value()) { - parent_->set_datapoint_value(*this->dimmer_id_, brightness_int); + parent_->set_integer_datapoint_value(*this->dimmer_id_, brightness_int); } if (this->switch_id_.has_value()) { - parent_->set_datapoint_value(*this->switch_id_, true); + parent_->set_boolean_datapoint_value(*this->switch_id_, true); } } diff --git a/esphome/components/tuya/switch/tuya_switch.cpp b/esphome/components/tuya/switch/tuya_switch.cpp index 8cd09fb01c..cbd794b001 100644 --- a/esphome/components/tuya/switch/tuya_switch.cpp +++ b/esphome/components/tuya/switch/tuya_switch.cpp @@ -15,7 +15,7 @@ void TuyaSwitch::setup() { void TuyaSwitch::write_state(bool state) { ESP_LOGV(TAG, "Setting switch %u: %s", this->switch_id_, ONOFF(state)); - this->parent_->set_datapoint_value(this->switch_id_, state); + this->parent_->set_boolean_datapoint_value(this->switch_id_, state); this->publish_state(state); } diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 9e036feda9..bbab4f04ce 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -437,42 +437,38 @@ void Tuya::send_local_time_() { } #endif -void Tuya::set_datapoint_value(uint8_t datapoint_id, uint32_t value) { - ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); +void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value) { + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { - ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id); + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::RAW) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); return; - } - if (datapoint->value_uint == value) { + } else if (datapoint->value_raw == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } - - std::vector data; - switch (datapoint->len) { - case 4: - data.push_back(value >> 24); - data.push_back(value >> 16); - case 2: - data.push_back(value >> 8); - case 1: - data.push_back(value >> 0); - break; - default: - ESP_LOGE(TAG, "Unexpected datapoint length %zu", datapoint->len); - return; - } - this->send_datapoint_command_(datapoint->id, datapoint->type, data); + this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value); } -void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) { +void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1); +} + +void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4); +} + +void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) { ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { - ESP_LOGE(TAG, "Attempt to set unknown datapoint %u", datapoint_id); - } - if (datapoint->value_string == value) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != TuyaDatapointType::STRING) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (datapoint->value_string == value) { ESP_LOGV(TAG, "Not sending unchanged value"); return; } @@ -483,6 +479,14 @@ void Tuya::set_datapoint_value(uint8_t datapoint_id, const std::string &value) { this->send_datapoint_command_(datapoint->id, datapoint->type, data); } +void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1); +} + +void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) { + this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length); +} + optional Tuya::get_datapoint_(uint8_t datapoint_id) { for (auto &datapoint : this->datapoints_) if (datapoint.id == datapoint_id) @@ -490,6 +494,37 @@ optional Tuya::get_datapoint_(uint8_t datapoint_id) { return {}; } +void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value, + uint8_t length) { + ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value); + optional datapoint = this->get_datapoint_(datapoint_id); + if (!datapoint.has_value()) { + ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); + } else if (datapoint->type != datapoint_type) { + ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id); + return; + } else if (datapoint->value_uint == value) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + + std::vector data; + switch (length) { + case 4: + data.push_back(value >> 24); + data.push_back(value >> 16); + case 2: + data.push_back(value >> 8); + case 1: + data.push_back(value >> 0); + break; + default: + ESP_LOGE(TAG, "Unexpected datapoint length %u", length); + return; + } + this->send_datapoint_command_(datapoint_id, datapoint_type, data); +} + void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data) { std::vector buffer; buffer.push_back(datapoint_id); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 7ce4be4315..785399502b 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -17,7 +17,7 @@ enum class TuyaDatapointType : uint8_t { INTEGER = 0x02, // 4 byte STRING = 0x03, // variable length ENUM = 0x04, // 1 byte - BITMASK = 0x05, // 2 bytes + BITMASK = 0x05, // 1/2/4 bytes }; struct TuyaDatapoint { @@ -75,8 +75,12 @@ class Tuya : public Component, public uart::UARTDevice { void loop() override; void dump_config() override; void register_listener(uint8_t datapoint_id, const std::function &func); - void set_datapoint_value(uint8_t datapoint_id, uint32_t value); - void set_datapoint_value(uint8_t datapoint_id, const std::string &value); + void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); + void set_boolean_datapoint_value(uint8_t datapoint_id, bool value); + void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); + void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); + void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); #ifdef USE_TIME void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif @@ -95,6 +99,8 @@ class Tuya : public Component, public uart::UARTDevice { void process_command_queue_(); void send_command_(const TuyaCommand &command); void send_empty_command_(TuyaCommandType command); + void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, + uint8_t length); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); void send_wifi_status_(); From d3375193a9106e9da0ca739179f4e16a7b7bb3d3 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:48:32 +0200 Subject: [PATCH 091/285] Feature pipsolar anh (#1664) Co-authored-by: Andreas Hergert Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/pipsolar/__init__.py | 32 + .../pipsolar/binary_sensor/__init__.py | 144 +++ .../components/pipsolar/output/__init__.py | 106 ++ .../pipsolar/output/pipsolar_output.cpp | 22 + .../pipsolar/output/pipsolar_output.h | 40 + esphome/components/pipsolar/pipsolar.cpp | 922 ++++++++++++++++++ esphome/components/pipsolar/pipsolar.h | 223 +++++ .../components/pipsolar/sensor/__init__.py | 220 +++++ .../components/pipsolar/switch/__init__.py | 60 ++ .../pipsolar/switch/pipsolar_switch.cpp | 24 + .../pipsolar/switch/pipsolar_switch.h | 25 + .../pipsolar/text_sensor/__init__.py | 52 + .../text_sensor/pipsolar_textsensor.cpp | 13 + .../text_sensor/pipsolar_textsensor.h | 20 + tests/test4.yaml | 228 +++++ 16 files changed, 2132 insertions(+) create mode 100644 esphome/components/pipsolar/__init__.py create mode 100644 esphome/components/pipsolar/binary_sensor/__init__.py create mode 100644 esphome/components/pipsolar/output/__init__.py create mode 100644 esphome/components/pipsolar/output/pipsolar_output.cpp create mode 100644 esphome/components/pipsolar/output/pipsolar_output.h create mode 100644 esphome/components/pipsolar/pipsolar.cpp create mode 100644 esphome/components/pipsolar/pipsolar.h create mode 100644 esphome/components/pipsolar/sensor/__init__.py create mode 100644 esphome/components/pipsolar/switch/__init__.py create mode 100644 esphome/components/pipsolar/switch/pipsolar_switch.cpp create mode 100644 esphome/components/pipsolar/switch/pipsolar_switch.h create mode 100644 esphome/components/pipsolar/text_sensor/__init__.py create mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp create mode 100644 esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 714ef58538..605222df63 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -87,6 +87,7 @@ esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter +esphome/components/pipsolar/* @andreashergert1984 esphome/components/pmsa003i/* @sjtrny esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py new file mode 100644 index 0000000000..20e4672125 --- /dev/null +++ b/esphome/components/pipsolar/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.components import uart + +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@andreashergert1984"] +AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "output"] +MULTI_CONF = True + +CONF_PIPSOLAR_ID = "pipsolar_id" + +pipsolar_ns = cg.esphome_ns.namespace("pipsolar") +PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component) + +PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent), + } +) + +CONFIG_SCHEMA = cv.All( + cv.Schema({cv.GenerateID(): cv.declare_id(PipsolarComponent)}) + .extend(cv.polling_component_schema("1s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield uart.register_uart_device(var, config) diff --git a/esphome/components/pipsolar/binary_sensor/__init__.py b/esphome/components/pipsolar/binary_sensor/__init__.py new file mode 100644 index 0000000000..5c6af3bffc --- /dev/null +++ b/esphome/components/pipsolar/binary_sensor/__init__.py @@ -0,0 +1,144 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_ID, +) +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID + +DEPENDENCIES = ["uart"] + +CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version" +CONF_CONFIGURATION_STATUS = "configuration_status" +CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version" +CONF_LOAD_STATUS = "load_status" +CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING = ( + "battery_voltage_to_steady_while_charging" +) +CONF_CHARGING_STATUS = "charging_status" +CONF_SCC_CHARGING_STATUS = "scc_charging_status" +CONF_AC_CHARGING_STATUS = "ac_charging_status" +CONF_CHARGING_TO_FLOATING_MODE = "charging_to_floating_mode" +CONF_SWITCH_ON = "switch_on" +CONF_DUSTPROOF_INSTALLED = "dustproof_installed" +CONF_SILENCE_BUZZER_OPEN_BUZZER = "silence_buzzer_open_buzzer" +CONF_OVERLOAD_BYPASS_FUNCTION = "overload_bypass_function" +CONF_LCD_ESCAPE_TO_DEFAULT = "lcd_escape_to_default" +CONF_OVERLOAD_RESTART_FUNCTION = "overload_restart_function" +CONF_OVER_TEMPERATURE_RESTART_FUNCTION = "over_temperature_restart_function" +CONF_BACKLIGHT_ON = "backlight_on" +CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT = "alarm_on_when_primary_source_interrupt" +CONF_FAULT_CODE_RECORD = "fault_code_record" +CONF_POWER_SAVING = "power_saving" + +CONF_WARNINGS_PRESENT = "warnings_present" +CONF_FAULTS_PRESENT = "faults_present" +CONF_WARNING_POWER_LOSS = "warning_power_loss" +CONF_FAULT_INVERTER_FAULT = "fault_inverter_fault" +CONF_FAULT_BUS_OVER = "fault_bus_over" +CONF_FAULT_BUS_UNDER = "fault_bus_under" +CONF_FAULT_BUS_SOFT_FAIL = "fault_bus_soft_fail" +CONF_WARNING_LINE_FAIL = "warning_line_fail" +CONF_FAULT_OPVSHORT = "fault_opvshort" +CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW = "fault_inverter_voltage_too_low" +CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH = "fault_inverter_voltage_too_high" +CONF_WARNING_OVER_TEMPERATURE = "warning_over_temperature" +CONF_WARNING_FAN_LOCK = "warning_fan_lock" +CONF_WARNING_BATTERY_VOLTAGE_HIGH = "warning_battery_voltage_high" +CONF_WARNING_BATTERY_LOW_ALARM = "warning_battery_low_alarm" +CONF_WARNING_BATTERY_UNDER_SHUTDOWN = "warning_battery_under_shutdown" +CONF_WARNING_BATTERY_DERATING = "warning_battery_derating" +CONF_WARNING_OVER_LOAD = "warning_over_load" +CONF_WARNING_EEPROM_FAILED = "warning_eeprom_failed" +CONF_FAULT_INVERTER_OVER_CURRENT = "fault_inverter_over_current" +CONF_FAULT_INVERTER_SOFT_FAILED = "fault_inverter_soft_failed" +CONF_FAULT_SELF_TEST_FAILED = "fault_self_test_failed" +CONF_FAULT_OP_DC_VOLTAGE_OVER = "fault_op_dc_voltage_over" +CONF_FAULT_BATTERY_OPEN = "fault_battery_open" +CONF_FAULT_CURRENT_SENSOR_FAILED = "fault_current_sensor_failed" +CONF_FAULT_BATTERY_SHORT = "fault_battery_short" +CONF_WARNING_POWER_LIMIT = "warning_power_limit" +CONF_WARNING_PV_VOLTAGE_HIGH = "warning_pv_voltage_high" +CONF_FAULT_MPPT_OVERLOAD = "fault_mppt_overload" +CONF_WARNING_MPPT_OVERLOAD = "warning_mppt_overload" +CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE = "warning_battery_too_low_to_charge" +CONF_FAULT_DC_DC_OVER_CURRENT = "fault_dc_dc_over_current" +CONF_FAULT_CODE = "fault_code" +CONF_WARNUNG_LOW_PV_ENERGY = "warnung_low_pv_energy" +CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START = ( + "warning_high_ac_input_during_bus_soft_start" +) +CONF_WARNING_BATTERY_EQUALIZATION = "warning_battery_equalization" + +TYPES = [ + CONF_ADD_SBU_PRIORITY_VERSION, + CONF_CONFIGURATION_STATUS, + CONF_SCC_FIRMWARE_VERSION, + CONF_LOAD_STATUS, + CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING, + CONF_CHARGING_STATUS, + CONF_SCC_CHARGING_STATUS, + CONF_AC_CHARGING_STATUS, + CONF_CHARGING_TO_FLOATING_MODE, + CONF_SWITCH_ON, + CONF_DUSTPROOF_INSTALLED, + CONF_SILENCE_BUZZER_OPEN_BUZZER, + CONF_OVERLOAD_BYPASS_FUNCTION, + CONF_LCD_ESCAPE_TO_DEFAULT, + CONF_OVERLOAD_RESTART_FUNCTION, + CONF_OVER_TEMPERATURE_RESTART_FUNCTION, + CONF_BACKLIGHT_ON, + CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT, + CONF_FAULT_CODE_RECORD, + CONF_POWER_SAVING, + CONF_WARNINGS_PRESENT, + CONF_FAULTS_PRESENT, + CONF_WARNING_POWER_LOSS, + CONF_FAULT_INVERTER_FAULT, + CONF_FAULT_BUS_OVER, + CONF_FAULT_BUS_UNDER, + CONF_FAULT_BUS_SOFT_FAIL, + CONF_WARNING_LINE_FAIL, + CONF_FAULT_OPVSHORT, + CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW, + CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH, + CONF_WARNING_OVER_TEMPERATURE, + CONF_WARNING_FAN_LOCK, + CONF_WARNING_BATTERY_VOLTAGE_HIGH, + CONF_WARNING_BATTERY_LOW_ALARM, + CONF_WARNING_BATTERY_UNDER_SHUTDOWN, + CONF_WARNING_BATTERY_DERATING, + CONF_WARNING_OVER_LOAD, + CONF_WARNING_EEPROM_FAILED, + CONF_FAULT_INVERTER_OVER_CURRENT, + CONF_FAULT_INVERTER_SOFT_FAILED, + CONF_FAULT_SELF_TEST_FAILED, + CONF_FAULT_OP_DC_VOLTAGE_OVER, + CONF_FAULT_BATTERY_OPEN, + CONF_FAULT_CURRENT_SENSOR_FAILED, + CONF_FAULT_BATTERY_SHORT, + CONF_WARNING_POWER_LIMIT, + CONF_WARNING_PV_VOLTAGE_HIGH, + CONF_FAULT_MPPT_OVERLOAD, + CONF_WARNING_MPPT_OVERLOAD, + CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE, + CONF_FAULT_DC_DC_OVER_CURRENT, + CONF_FAULT_CODE, + CONF_WARNUNG_LOW_PV_ENERGY, + CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START, + CONF_WARNING_BATTERY_EQUALIZATION, +] + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + for type in TYPES: + if type in config: + conf = config[type] + sens = cg.new_Pvariable(conf[CONF_ID]) + await binary_sensor.register_binary_sensor(sens, conf) + cg.add(getattr(paren, f"set_{type}")(sens)) diff --git a/esphome/components/pipsolar/output/__init__.py b/esphome/components/pipsolar/output/__init__.py new file mode 100644 index 0000000000..b518d485e7 --- /dev/null +++ b/esphome/components/pipsolar/output/__init__.py @@ -0,0 +1,106 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import output +from esphome.const import CONF_ID, CONF_VALUE +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID, pipsolar_ns + +DEPENDENCIES = ["pipsolar"] + +PipsolarOutput = pipsolar_ns.class_("PipsolarOutput", output.FloatOutput) +SetOutputAction = pipsolar_ns.class_("SetOutputAction", automation.Action) + +CONF_POSSIBLE_VALUES = "possible_values" + +# 3.11 PCVV: Setting battery C.V. (constant voltage) charging voltage 48.0V ~ 58.4V for 48V unit +# battery_bulk_voltage; +# battery_recharge_voltage; 12V unit: 11V/11.3V/11.5V/11.8V/12V/12.3V/12.5V/12.8V +# 24V unit: 22V/22.5V/23V/23.5V/24V/24.5V/25V/25.5V +# 48V unit: 44V/45V/46V/47V/48V/49V/50V/51V +# battery_under_voltage; 40.0V ~ 48.0V for 48V unit +# battery_float_voltage; 48.0V ~ 58.4V for 48V unit +# battery_type; 00 for AGM, 01 for Flooded battery +# current_max_ac_charging_current; +# output_source_priority; 00 / 01 / 02 +# charger_source_priority; For HS: 00 for utility first, 01 for solar first, 02 for solar and utility, 03 for only solar charging +# For MS/MSX: 00 for utility first, 01 for solar first, 03 for only solar charging +# battery_redischarge_voltage; 12V unit: 00.0V12V/12.3V/12.5V/12.8V/13V/13.3V/13.5V/13.8V/14V/14.3V/14.5 +# 24V unit: 00.0V/24V/24.5V/25V/25.5V/26V/26.5V/27V/27.5V/28V/28.5V/29V +# 48V unit: 00.0V48V/49V/50V/51V/52V/53V/54V/55V/56V/57V/58V + +CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage" +CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage" +CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage" +CONF_BATTERY_TYPE = "battery_type" +CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current" +CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current" +CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority" +CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority" +CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage" + +TYPES = { + CONF_BATTERY_RECHARGE_VOLTAGE: ( + [44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0], + "PBCV%02.1f", + ), + CONF_BATTERY_UNDER_VOLTAGE: ( + [40.0, 40.1, 42, 43, 44, 45, 46, 47, 48.0], + "PSDV%02.1f", + ), + CONF_BATTERY_FLOAT_VOLTAGE: ([48.0, 49.0, 50.0, 51.0], "PBFT%02.1f"), + CONF_BATTERY_TYPE: ([0, 1, 2], "PBT%02.0f"), + CONF_CURRENT_MAX_AC_CHARGING_CURRENT: ([2, 10, 20], "MUCHGC0%02.0f"), + CONF_CURRENT_MAX_CHARGING_CURRENT: ([10, 20, 30, 40], "MCHGC0%02.0f"), + CONF_OUTPUT_SOURCE_PRIORITY: ([0, 1, 2], "POP%02.0f"), + CONF_CHARGER_SOURCE_PRIORITY: ([0, 1, 2, 3], "PCP%02.0f"), + CONF_BATTERY_REDISCHARGE_VOLTAGE: ( + [0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58], + "PBDV%02.1f", + ), +} + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + { + cv.Optional(type): output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(PipsolarOutput), + cv.Optional(CONF_POSSIBLE_VALUES, default=values): cv.All( + cv.ensure_list(cv.positive_float), cv.Length(min=1) + ), + } + ) + for type, (values, _) in TYPES.items() + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, (_, command) in TYPES.items(): + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await output.register_output(var, conf) + cg.add(var.set_parent(paren)) + cg.add(var.set_set_command(command)) + if (CONF_POSSIBLE_VALUES) in conf: + cg.add(var.set_possible_values(conf[CONF_POSSIBLE_VALUES])) + + +@automation.register_action( + "output.pipsolar.set_level", + SetOutputAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(CONF_ID), + cv.Required(CONF_VALUE): cv.templatable(cv.positive_float), + } + ), +) +def output_pipsolar_set_level_to_code(config, action_id, template_arg, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = yield cg.templatable(config[CONF_VALUE], args, float) + cg.add(var.set_level(template_)) + yield var diff --git a/esphome/components/pipsolar/output/pipsolar_output.cpp b/esphome/components/pipsolar/output/pipsolar_output.cpp new file mode 100644 index 0000000000..b843f1f3e6 --- /dev/null +++ b/esphome/components/pipsolar/output/pipsolar_output.cpp @@ -0,0 +1,22 @@ +#include "pipsolar_output.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.output"; + +void PipsolarOutput::write_state(float state) { + char tmp[10]; + sprintf(tmp, this->set_command_.c_str(), state); + + if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) { + ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state); + this->parent_->switch_command(std::string(tmp)); + } else { + ESP_LOGD(TAG, "Will not write: %s as it is not in list of allowed values", tmp); + } +} +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/output/pipsolar_output.h b/esphome/components/pipsolar/output/pipsolar_output.h new file mode 100644 index 0000000000..932efe01c2 --- /dev/null +++ b/esphome/components/pipsolar/output/pipsolar_output.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { + +class Pipsolar; + +class PipsolarOutput : public output::FloatOutput { + public: + PipsolarOutput() {} + void set_parent(Pipsolar *parent) { this->parent_ = parent; } + void set_set_command(std::string command) { this->set_command_ = std::move(command); }; + void set_possible_values(std::vector possible_values) { this->possible_values_ = std::move(possible_values); } + void set_value(float value) { this->write_state(value); }; + + protected: + void write_state(float state) override; + std::string set_command_; + Pipsolar *parent_; + std::vector possible_values_; +}; + +template class SetOutputAction : public Action { + public: + SetOutputAction(PipsolarOutput *output) : output_(output) {} + + TEMPLATABLE_VALUE(float, level) + + void play(Ts... x) override { this->output_->set_value(this->level_.value(x...)); } + + protected: + PipsolarOutput *output_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp new file mode 100644 index 0000000000..9c7adc13c1 --- /dev/null +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -0,0 +1,922 @@ +#include "pipsolar.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar"; + +void Pipsolar::setup() { + this->state_ = STATE_IDLE; + this->command_start_millis_ = 0; +} + +void Pipsolar::empty_uart_buffer_() { + uint8_t byte; + while (this->available()) { + this->read_byte(&byte); + } +} + +void Pipsolar::loop() { + // Read message + if (this->state_ == STATE_IDLE) { + this->empty_uart_buffer_(); + switch (this->send_next_command_()) { + case 0: + // no command send (empty queue) time to poll + if (millis() - this->last_poll_ > this->update_interval_) { + this->send_next_poll_(); + this->last_poll_ = millis(); + } + return; + break; + case 1: + // command send + return; + break; + } + } + if (this->state_ == STATE_COMMAND_COMPLETE) { + if (this->check_incoming_length_(4)) { + ESP_LOGD(TAG, "response length for command OK"); + if (this->check_incoming_crc_()) { + // crc ok + if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') { + ESP_LOGD(TAG, "command successful"); + } else { + ESP_LOGD(TAG, "command not successful"); + } + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + + } else { + // crc failed + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + } + } else { + ESP_LOGD(TAG, "response length for command %s not OK: with length %zu", + this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_); + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + } + } + + if (this->state_ == STATE_POLL_DECODED) { + std::string mode; + switch (this->used_polling_commands_[this->last_polling_command_].identifier) { + case POLLING_QPIRI: + if (this->grid_rating_voltage_) { + this->grid_rating_voltage_->publish_state(value_grid_rating_voltage_); + } + if (this->grid_rating_current_) { + this->grid_rating_current_->publish_state(value_grid_rating_current_); + } + if (this->ac_output_rating_voltage_) { + this->ac_output_rating_voltage_->publish_state(value_ac_output_rating_voltage_); + } + if (this->ac_output_rating_frequency_) { + this->ac_output_rating_frequency_->publish_state(value_ac_output_rating_frequency_); + } + if (this->ac_output_rating_current_) { + this->ac_output_rating_current_->publish_state(value_ac_output_rating_current_); + } + if (this->ac_output_rating_apparent_power_) { + this->ac_output_rating_apparent_power_->publish_state(value_ac_output_rating_apparent_power_); + } + if (this->ac_output_rating_active_power_) { + this->ac_output_rating_active_power_->publish_state(value_ac_output_rating_active_power_); + } + if (this->battery_rating_voltage_) { + this->battery_rating_voltage_->publish_state(value_battery_rating_voltage_); + } + if (this->battery_recharge_voltage_) { + this->battery_recharge_voltage_->publish_state(value_battery_recharge_voltage_); + } + if (this->battery_under_voltage_) { + this->battery_under_voltage_->publish_state(value_battery_under_voltage_); + } + if (this->battery_bulk_voltage_) { + this->battery_bulk_voltage_->publish_state(value_battery_bulk_voltage_); + } + if (this->battery_float_voltage_) { + this->battery_float_voltage_->publish_state(value_battery_float_voltage_); + } + if (this->battery_type_) { + this->battery_type_->publish_state(value_battery_type_); + } + if (this->current_max_ac_charging_current_) { + this->current_max_ac_charging_current_->publish_state(value_current_max_ac_charging_current_); + } + if (this->current_max_charging_current_) { + this->current_max_charging_current_->publish_state(value_current_max_charging_current_); + } + if (this->input_voltage_range_) { + this->input_voltage_range_->publish_state(value_input_voltage_range_); + } + // special for input voltage range switch + if (this->input_voltage_range_switch_) { + this->input_voltage_range_switch_->publish_state(value_input_voltage_range_ == 1); + } + if (this->output_source_priority_) { + this->output_source_priority_->publish_state(value_output_source_priority_); + } + // special for output source priority switches + if (this->output_source_priority_utility_switch_) { + this->output_source_priority_utility_switch_->publish_state(value_output_source_priority_ == 0); + } + if (this->output_source_priority_solar_switch_) { + this->output_source_priority_solar_switch_->publish_state(value_output_source_priority_ == 1); + } + if (this->output_source_priority_battery_switch_) { + this->output_source_priority_battery_switch_->publish_state(value_output_source_priority_ == 2); + } + if (this->charger_source_priority_) { + this->charger_source_priority_->publish_state(value_charger_source_priority_); + } + if (this->parallel_max_num_) { + this->parallel_max_num_->publish_state(value_parallel_max_num_); + } + if (this->machine_type_) { + this->machine_type_->publish_state(value_machine_type_); + } + if (this->topology_) { + this->topology_->publish_state(value_topology_); + } + if (this->output_mode_) { + this->output_mode_->publish_state(value_output_mode_); + } + if (this->battery_redischarge_voltage_) { + this->battery_redischarge_voltage_->publish_state(value_battery_redischarge_voltage_); + } + if (this->pv_ok_condition_for_parallel_) { + this->pv_ok_condition_for_parallel_->publish_state(value_pv_ok_condition_for_parallel_); + } + // special for pv ok condition switch + if (this->pv_ok_condition_for_parallel_switch_) { + this->pv_ok_condition_for_parallel_switch_->publish_state(value_pv_ok_condition_for_parallel_ == 1); + } + if (this->pv_power_balance_) { + this->pv_power_balance_->publish_state(value_pv_power_balance_ == 1); + } + // special for power balance switch + if (this->pv_power_balance_switch_) { + this->pv_power_balance_switch_->publish_state(value_pv_power_balance_ == 1); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QPIGS: + if (this->grid_voltage_) { + this->grid_voltage_->publish_state(value_grid_voltage_); + } + if (this->grid_frequency_) { + this->grid_frequency_->publish_state(value_grid_frequency_); + } + if (this->ac_output_voltage_) { + this->ac_output_voltage_->publish_state(value_ac_output_voltage_); + } + if (this->ac_output_frequency_) { + this->ac_output_frequency_->publish_state(value_ac_output_frequency_); + } + if (this->ac_output_apparent_power_) { + this->ac_output_apparent_power_->publish_state(value_ac_output_apparent_power_); + } + if (this->ac_output_active_power_) { + this->ac_output_active_power_->publish_state(value_ac_output_active_power_); + } + if (this->output_load_percent_) { + this->output_load_percent_->publish_state(value_output_load_percent_); + } + if (this->bus_voltage_) { + this->bus_voltage_->publish_state(value_bus_voltage_); + } + if (this->battery_voltage_) { + this->battery_voltage_->publish_state(value_battery_voltage_); + } + if (this->battery_charging_current_) { + this->battery_charging_current_->publish_state(value_battery_charging_current_); + } + if (this->battery_capacity_percent_) { + this->battery_capacity_percent_->publish_state(value_battery_capacity_percent_); + } + if (this->inverter_heat_sink_temperature_) { + this->inverter_heat_sink_temperature_->publish_state(value_inverter_heat_sink_temperature_); + } + if (this->pv_input_current_for_battery_) { + this->pv_input_current_for_battery_->publish_state(value_pv_input_current_for_battery_); + } + if (this->pv_input_voltage_) { + this->pv_input_voltage_->publish_state(value_pv_input_voltage_); + } + if (this->battery_voltage_scc_) { + this->battery_voltage_scc_->publish_state(value_battery_voltage_scc_); + } + if (this->battery_discharge_current_) { + this->battery_discharge_current_->publish_state(value_battery_discharge_current_); + } + if (this->add_sbu_priority_version_) { + this->add_sbu_priority_version_->publish_state(value_add_sbu_priority_version_); + } + if (this->configuration_status_) { + this->configuration_status_->publish_state(value_configuration_status_); + } + if (this->scc_firmware_version_) { + this->scc_firmware_version_->publish_state(value_scc_firmware_version_); + } + if (this->load_status_) { + this->load_status_->publish_state(value_load_status_); + } + if (this->battery_voltage_to_steady_while_charging_) { + this->battery_voltage_to_steady_while_charging_->publish_state( + value_battery_voltage_to_steady_while_charging_); + } + if (this->charging_status_) { + this->charging_status_->publish_state(value_charging_status_); + } + if (this->scc_charging_status_) { + this->scc_charging_status_->publish_state(value_scc_charging_status_); + } + if (this->ac_charging_status_) { + this->ac_charging_status_->publish_state(value_ac_charging_status_); + } + if (this->battery_voltage_offset_for_fans_on_) { + this->battery_voltage_offset_for_fans_on_->publish_state(value_battery_voltage_offset_for_fans_on_ / 10.0f); + } //.1 scale + if (this->eeprom_version_) { + this->eeprom_version_->publish_state(value_eeprom_version_); + } + if (this->pv_charging_power_) { + this->pv_charging_power_->publish_state(value_pv_charging_power_); + } + if (this->charging_to_floating_mode_) { + this->charging_to_floating_mode_->publish_state(value_charging_to_floating_mode_); + } + if (this->switch_on_) { + this->switch_on_->publish_state(value_switch_on_); + } + if (this->dustproof_installed_) { + this->dustproof_installed_->publish_state(value_dustproof_installed_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QMOD: + if (this->device_mode_) { + mode = value_device_mode_; + this->device_mode_->publish_state(mode); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QFLAG: + if (this->silence_buzzer_open_buzzer_) { + this->silence_buzzer_open_buzzer_->publish_state(value_silence_buzzer_open_buzzer_); + } + if (this->overload_bypass_function_) { + this->overload_bypass_function_->publish_state(value_overload_bypass_function_); + } + if (this->lcd_escape_to_default_) { + this->lcd_escape_to_default_->publish_state(value_lcd_escape_to_default_); + } + if (this->overload_restart_function_) { + this->overload_restart_function_->publish_state(value_overload_restart_function_); + } + if (this->over_temperature_restart_function_) { + this->over_temperature_restart_function_->publish_state(value_over_temperature_restart_function_); + } + if (this->backlight_on_) { + this->backlight_on_->publish_state(value_backlight_on_); + } + if (this->alarm_on_when_primary_source_interrupt_) { + this->alarm_on_when_primary_source_interrupt_->publish_state(value_alarm_on_when_primary_source_interrupt_); + } + if (this->fault_code_record_) { + this->fault_code_record_->publish_state(value_fault_code_record_); + } + if (this->power_saving_) { + this->power_saving_->publish_state(value_power_saving_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QPIWS: + if (this->warnings_present_) { + this->warnings_present_->publish_state(value_warnings_present_); + } + if (this->faults_present_) { + this->faults_present_->publish_state(value_faults_present_); + } + if (this->warning_power_loss_) { + this->warning_power_loss_->publish_state(value_warning_power_loss_); + } + if (this->fault_inverter_fault_) { + this->fault_inverter_fault_->publish_state(value_fault_inverter_fault_); + } + if (this->fault_bus_over_) { + this->fault_bus_over_->publish_state(value_fault_bus_over_); + } + if (this->fault_bus_under_) { + this->fault_bus_under_->publish_state(value_fault_bus_under_); + } + if (this->fault_bus_soft_fail_) { + this->fault_bus_soft_fail_->publish_state(value_fault_bus_soft_fail_); + } + if (this->warning_line_fail_) { + this->warning_line_fail_->publish_state(value_warning_line_fail_); + } + if (this->fault_opvshort_) { + this->fault_opvshort_->publish_state(value_fault_opvshort_); + } + if (this->fault_inverter_voltage_too_low_) { + this->fault_inverter_voltage_too_low_->publish_state(value_fault_inverter_voltage_too_low_); + } + if (this->fault_inverter_voltage_too_high_) { + this->fault_inverter_voltage_too_high_->publish_state(value_fault_inverter_voltage_too_high_); + } + if (this->warning_over_temperature_) { + this->warning_over_temperature_->publish_state(value_warning_over_temperature_); + } + if (this->warning_fan_lock_) { + this->warning_fan_lock_->publish_state(value_warning_fan_lock_); + } + if (this->warning_battery_voltage_high_) { + this->warning_battery_voltage_high_->publish_state(value_warning_battery_voltage_high_); + } + if (this->warning_battery_low_alarm_) { + this->warning_battery_low_alarm_->publish_state(value_warning_battery_low_alarm_); + } + if (this->warning_battery_under_shutdown_) { + this->warning_battery_under_shutdown_->publish_state(value_warning_battery_under_shutdown_); + } + if (this->warning_battery_derating_) { + this->warning_battery_derating_->publish_state(value_warning_battery_derating_); + } + if (this->warning_over_load_) { + this->warning_over_load_->publish_state(value_warning_over_load_); + } + if (this->warning_eeprom_failed_) { + this->warning_eeprom_failed_->publish_state(value_warning_eeprom_failed_); + } + if (this->fault_inverter_over_current_) { + this->fault_inverter_over_current_->publish_state(value_fault_inverter_over_current_); + } + if (this->fault_inverter_soft_failed_) { + this->fault_inverter_soft_failed_->publish_state(value_fault_inverter_soft_failed_); + } + if (this->fault_self_test_failed_) { + this->fault_self_test_failed_->publish_state(value_fault_self_test_failed_); + } + if (this->fault_op_dc_voltage_over_) { + this->fault_op_dc_voltage_over_->publish_state(value_fault_op_dc_voltage_over_); + } + if (this->fault_battery_open_) { + this->fault_battery_open_->publish_state(value_fault_battery_open_); + } + if (this->fault_current_sensor_failed_) { + this->fault_current_sensor_failed_->publish_state(value_fault_current_sensor_failed_); + } + if (this->fault_battery_short_) { + this->fault_battery_short_->publish_state(value_fault_battery_short_); + } + if (this->warning_power_limit_) { + this->warning_power_limit_->publish_state(value_warning_power_limit_); + } + if (this->warning_pv_voltage_high_) { + this->warning_pv_voltage_high_->publish_state(value_warning_pv_voltage_high_); + } + if (this->fault_mppt_overload_) { + this->fault_mppt_overload_->publish_state(value_fault_mppt_overload_); + } + if (this->warning_mppt_overload_) { + this->warning_mppt_overload_->publish_state(value_warning_mppt_overload_); + } + if (this->warning_battery_too_low_to_charge_) { + this->warning_battery_too_low_to_charge_->publish_state(value_warning_battery_too_low_to_charge_); + } + if (this->fault_dc_dc_over_current_) { + this->fault_dc_dc_over_current_->publish_state(value_fault_dc_dc_over_current_); + } + if (this->fault_code_) { + this->fault_code_->publish_state(value_fault_code_); + } + if (this->warnung_low_pv_energy_) { + this->warnung_low_pv_energy_->publish_state(value_warnung_low_pv_energy_); + } + if (this->warning_high_ac_input_during_bus_soft_start_) { + this->warning_high_ac_input_during_bus_soft_start_->publish_state( + value_warning_high_ac_input_during_bus_soft_start_); + } + if (this->warning_battery_equalization_) { + this->warning_battery_equalization_->publish_state(value_warning_battery_equalization_); + } + this->state_ = STATE_IDLE; + break; + case POLLING_QT: + this->state_ = STATE_IDLE; + break; + case POLLING_QMN: + this->state_ = STATE_IDLE; + break; + } + } + + if (this->state_ == STATE_POLL_CHECKED) { + bool enabled = true; + std::string fc; + char tmp[PIPSOLAR_READ_BUFFER_LENGTH]; + sprintf(tmp, "%s", this->read_buffer_); + switch (this->used_polling_commands_[this->last_polling_command_].identifier) { + case POLLING_QPIRI: + ESP_LOGD(TAG, "Decode QPIRI"); + sscanf(tmp, "(%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d %d %d %d %d %f %d %d", // NOLINT + &value_grid_rating_voltage_, &value_grid_rating_current_, &value_ac_output_rating_voltage_, // NOLINT + &value_ac_output_rating_frequency_, &value_ac_output_rating_current_, // NOLINT + &value_ac_output_rating_apparent_power_, &value_ac_output_rating_active_power_, // NOLINT + &value_battery_rating_voltage_, &value_battery_recharge_voltage_, // NOLINT + &value_battery_under_voltage_, &value_battery_bulk_voltage_, &value_battery_float_voltage_, // NOLINT + &value_battery_type_, &value_current_max_ac_charging_current_, // NOLINT + &value_current_max_charging_current_, &value_input_voltage_range_, // NOLINT + &value_output_source_priority_, &value_charger_source_priority_, &value_parallel_max_num_, // NOLINT + &value_machine_type_, &value_topology_, &value_output_mode_, // NOLINT + &value_battery_redischarge_voltage_, &value_pv_ok_condition_for_parallel_, // NOLINT + &value_pv_power_balance_); // NOLINT + if (this->last_qpiri_) { + this->last_qpiri_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QPIGS: + ESP_LOGD(TAG, "Decode QPIGS"); + sscanf( // NOLINT + tmp, // NOLINT + "(%f %f %f %f %d %d %d %d %f %d %d %d %d %f %f %d %1d%1d%1d%1d%1d%1d%1d%1d %d %d %d %1d%1d%1d", // NOLINT + &value_grid_voltage_, &value_grid_frequency_, &value_ac_output_voltage_, // NOLINT + &value_ac_output_frequency_, // NOLINT + &value_ac_output_apparent_power_, &value_ac_output_active_power_, &value_output_load_percent_, // NOLINT + &value_bus_voltage_, &value_battery_voltage_, &value_battery_charging_current_, // NOLINT + &value_battery_capacity_percent_, &value_inverter_heat_sink_temperature_, // NOLINT + &value_pv_input_current_for_battery_, &value_pv_input_voltage_, &value_battery_voltage_scc_, // NOLINT + &value_battery_discharge_current_, &value_add_sbu_priority_version_, // NOLINT + &value_configuration_status_, &value_scc_firmware_version_, &value_load_status_, // NOLINT + &value_battery_voltage_to_steady_while_charging_, &value_charging_status_, // NOLINT + &value_scc_charging_status_, &value_ac_charging_status_, // NOLINT + &value_battery_voltage_offset_for_fans_on_, &value_eeprom_version_, &value_pv_charging_power_, // NOLINT + &value_charging_to_floating_mode_, &value_switch_on_, // NOLINT + &value_dustproof_installed_); // NOLINT + if (this->last_qpigs_) { + this->last_qpigs_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QMOD: + ESP_LOGD(TAG, "Decode QMOD"); + this->value_device_mode_ = char(this->read_buffer_[1]); + if (this->last_qmod_) { + this->last_qmod_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QFLAG: + ESP_LOGD(TAG, "Decode QFLAG"); + // result like:"(EbkuvxzDajy" + // get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value + for (int i = 1; i < strlen(tmp); i++) { + switch (tmp[i]) { + case 'E': + enabled = true; + break; + case 'D': + enabled = false; + break; + case 'a': + this->value_silence_buzzer_open_buzzer_ = enabled; + break; + case 'b': + this->value_overload_bypass_function_ = enabled; + break; + case 'k': + this->value_lcd_escape_to_default_ = enabled; + break; + case 'u': + this->value_overload_restart_function_ = enabled; + break; + case 'v': + this->value_over_temperature_restart_function_ = enabled; + break; + case 'x': + this->value_backlight_on_ = enabled; + break; + case 'y': + this->value_alarm_on_when_primary_source_interrupt_ = enabled; + break; + case 'z': + this->value_fault_code_record_ = enabled; + break; + case 'j': + this->value_power_saving_ = enabled; + break; + } + } + if (this->last_qflag_) { + this->last_qflag_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QPIWS: + ESP_LOGD(TAG, "Decode QPIWS"); + // '(00000000000000000000000000000000' + // iterate over all available flag (as not all models have all flags, but at least in the same order) + this->value_warnings_present_ = false; + this->value_faults_present_ = true; + + for (int i = 1; i < strlen(tmp); i++) { + enabled = tmp[i] == '1'; + switch (i) { + case 1: + this->value_warning_power_loss_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 2: + this->value_fault_inverter_fault_ = enabled; + this->value_faults_present_ += enabled; + break; + case 3: + this->value_fault_bus_over_ = enabled; + this->value_faults_present_ += enabled; + break; + case 4: + this->value_fault_bus_under_ = enabled; + this->value_faults_present_ += enabled; + break; + case 5: + this->value_fault_bus_soft_fail_ = enabled; + this->value_faults_present_ += enabled; + break; + case 6: + this->value_warning_line_fail_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 7: + this->value_fault_opvshort_ = enabled; + this->value_faults_present_ += enabled; + break; + case 8: + this->value_fault_inverter_voltage_too_low_ = enabled; + this->value_faults_present_ += enabled; + break; + case 9: + this->value_fault_inverter_voltage_too_high_ = enabled; + this->value_faults_present_ += enabled; + break; + case 10: + this->value_warning_over_temperature_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 11: + this->value_warning_fan_lock_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 12: + this->value_warning_battery_voltage_high_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 13: + this->value_warning_battery_low_alarm_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 15: + this->value_warning_battery_under_shutdown_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 16: + this->value_warning_battery_derating_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 17: + this->value_warning_over_load_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 18: + this->value_warning_eeprom_failed_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 19: + this->value_fault_inverter_over_current_ = enabled; + this->value_faults_present_ += enabled; + break; + case 20: + this->value_fault_inverter_soft_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 21: + this->value_fault_self_test_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 22: + this->value_fault_op_dc_voltage_over_ = enabled; + this->value_faults_present_ += enabled; + break; + case 23: + this->value_fault_battery_open_ = enabled; + this->value_faults_present_ += enabled; + break; + case 24: + this->value_fault_current_sensor_failed_ = enabled; + this->value_faults_present_ += enabled; + break; + case 25: + this->value_fault_battery_short_ = enabled; + this->value_faults_present_ += enabled; + break; + case 26: + this->value_warning_power_limit_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 27: + this->value_warning_pv_voltage_high_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 28: + this->value_fault_mppt_overload_ = enabled; + this->value_faults_present_ += enabled; + break; + case 29: + this->value_warning_mppt_overload_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 30: + this->value_warning_battery_too_low_to_charge_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 31: + this->value_fault_dc_dc_over_current_ = enabled; + this->value_faults_present_ += enabled; + break; + case 32: + fc = tmp[i]; + fc += tmp[i + 1]; + this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10); + break; + case 34: + this->value_warnung_low_pv_energy_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 35: + this->value_warning_high_ac_input_during_bus_soft_start_ = enabled; + this->value_warnings_present_ += enabled; + break; + case 36: + this->value_warning_battery_equalization_ = enabled; + this->value_warnings_present_ += enabled; + break; + } + } + if (this->last_qpiws_) { + this->last_qpiws_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QT: + ESP_LOGD(TAG, "Decode QT"); + if (this->last_qt_) { + this->last_qt_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + case POLLING_QMN: + ESP_LOGD(TAG, "Decode QMN"); + if (this->last_qmn_) { + this->last_qmn_->publish_state(tmp); + } + this->state_ = STATE_POLL_DECODED; + break; + default: + this->state_ = STATE_IDLE; + break; + } + return; + } + + if (this->state_ == STATE_POLL_COMPLETE) { + if (this->check_incoming_crc_()) { + if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' && + this->read_buffer_[3] == 'K') { + this->state_ = STATE_IDLE; + return; + } + // crc ok + this->state_ = STATE_POLL_CHECKED; + return; + } else { + this->state_ = STATE_IDLE; + } + } + + if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) { + while (this->available()) { + uint8_t byte; + this->read_byte(&byte); + + if (this->read_pos_ == PIPSOLAR_READ_BUFFER_LENGTH) { + this->read_pos_ = 0; + this->empty_uart_buffer_(); + } + this->read_buffer_[this->read_pos_] = byte; + this->read_pos_++; + + // end of answer + if (byte == 0x0D) { + this->read_buffer_[this->read_pos_] = 0; + this->empty_uart_buffer_(); + if (this->state_ == STATE_POLL) { + this->state_ = STATE_POLL_COMPLETE; + } + if (this->state_ == STATE_COMMAND) { + this->state_ = STATE_COMMAND_COMPLETE; + } + } + } // available + } + if (this->state_ == STATE_COMMAND) { + if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) { + // command timeout + const char *command = this->command_queue_[this->command_queue_position_].c_str(); + this->command_start_millis_ = millis(); + ESP_LOGD(TAG, "timeout command from queue: %s", command); + this->command_queue_[this->command_queue_position_] = std::string(""); + this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH; + this->state_ = STATE_IDLE; + return; + } else { + } + } + if (this->state_ == STATE_POLL) { + if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) { + // command timeout + ESP_LOGD(TAG, "timeout command to poll: %s", this->used_polling_commands_[this->last_polling_command_].command); + this->state_ = STATE_IDLE; + } else { + } + } +} + +uint8_t Pipsolar::check_incoming_length_(uint8_t length) { + if (this->read_pos_ - 3 == length) { + return 1; + } + return 0; +} + +uint8_t Pipsolar::check_incoming_crc_() { + uint16_t crc16; + crc16 = calc_crc_(read_buffer_, read_pos_ - 3); + ESP_LOGD(TAG, "checking crc on incoming message"); + if (((uint8_t)((crc16) >> 8)) == read_buffer_[read_pos_ - 3] && + ((uint8_t)((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) { + ESP_LOGD(TAG, "CRC OK"); + read_buffer_[read_pos_ - 1] = 0; + read_buffer_[read_pos_ - 2] = 0; + read_buffer_[read_pos_ - 3] = 0; + return 1; + } + ESP_LOGD(TAG, "CRC NOK expected: %X %X but got: %X %X", ((uint8_t)((crc16) >> 8)), ((uint8_t)((crc16) &0xff)), + read_buffer_[read_pos_ - 3], read_buffer_[read_pos_ - 2]); + return 0; +} + +// send next command used +uint8_t Pipsolar::send_next_command_() { + uint16_t crc16; + if (this->command_queue_[this->command_queue_position_].length() != 0) { + const char *command = this->command_queue_[this->command_queue_position_].c_str(); + uint8_t byte_command[16]; + uint8_t length = this->command_queue_[this->command_queue_position_].length(); + for (uint8_t i = 0; i < length; i++) { + byte_command[i] = (uint8_t) this->command_queue_[this->command_queue_position_].at(i); + } + this->state_ = STATE_COMMAND; + this->command_start_millis_ = millis(); + this->empty_uart_buffer_(); + this->read_pos_ = 0; + crc16 = calc_crc_(byte_command, length); + this->write_str(command); + // checksum + this->write(((uint8_t)((crc16) >> 8))); // highbyte + this->write(((uint8_t)((crc16) &0xff))); // lowbyte + // end Byte + this->write(0x0D); + ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length); + return 1; + } + return 0; +} + +void Pipsolar::send_next_poll_() { + uint16_t crc16; + this->last_polling_command_ = (this->last_polling_command_ + 1) % 15; + if (this->used_polling_commands_[this->last_polling_command_].length == 0) { + this->last_polling_command_ = 0; + } + if (this->used_polling_commands_[this->last_polling_command_].length == 0) { + // no command specified + return; + } + this->state_ = STATE_POLL; + this->command_start_millis_ = millis(); + this->empty_uart_buffer_(); + this->read_pos_ = 0; + crc16 = calc_crc_(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + this->write_array(this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); + // checksum + this->write(((uint8_t)((crc16) >> 8))); // highbyte + this->write(((uint8_t)((crc16) &0xff))); // lowbyte + // end Byte + this->write(0x0D); + ESP_LOGD(TAG, "Sending polling command : %s with length %d", + this->used_polling_commands_[this->last_polling_command_].command, + this->used_polling_commands_[this->last_polling_command_].length); +} + +void Pipsolar::queue_command_(const char *command, byte length) { + uint8_t next_position = command_queue_position_; + for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { + uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; + if (command_queue_[testposition].length() == 0) { + command_queue_[testposition] = command; + ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command, + command_queue_[testposition].length(), testposition); + return; + } + } + ESP_LOGD(TAG, "Command queue full dropping command: %s", command); +} + +void Pipsolar::switch_command(const std::string &command) { + ESP_LOGD(TAG, "got command: %s", command.c_str()); + queue_command_(command.c_str(), command.length()); +} +void Pipsolar::dump_config() { + ESP_LOGCONFIG(TAG, "Pipsolar:"); + ESP_LOGCONFIG(TAG, "used commands:"); + for (auto &used_polling_command : this->used_polling_commands_) { + if (used_polling_command.length != 0) { + ESP_LOGCONFIG(TAG, "%s", used_polling_command.command); + } + } +} +void Pipsolar::update() {} + +void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) { + for (auto &used_polling_command : this->used_polling_commands_) { + if (used_polling_command.length == strlen(command)) { + uint8_t len = strlen(command); + if (memcmp(used_polling_command.command, command, len) == 0) { + return; + } + } + if (used_polling_command.length == 0) { + size_t length = strlen(command) + 1; + const char *beg = command; + const char *end = command + length; + used_polling_command.command = new uint8_t[length]; + size_t i = 0; + for (; beg != end; ++beg, ++i) { + used_polling_command.command[i] = (uint8_t)(*beg); + } + used_polling_command.errors = 0; + used_polling_command.identifier = polling_command; + used_polling_command.length = length - 1; + return; + } + } +} + +uint16_t Pipsolar::calc_crc_(uint8_t *msg, int n) { + // Initial value. xmodem uses 0xFFFF but this example + // requires an initial value of zero. + uint16_t x = 0; + while (n--) { + x = crc_xmodem_update_(x, (uint16_t) *msg++); + } + return (x); +} + +// See bottom of this page: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html +// Polynomial: x^16 + x^12 + x^5 + 1 (0x1021) +uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) { + int i; + crc = crc ^ ((uint16_t) data << 8); + for (i = 0; i < 8; i++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021) + else + crc <<= 1; + } + return crc; +} + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h new file mode 100644 index 0000000000..508036c7de --- /dev/null +++ b/esphome/components/pipsolar/pipsolar.h @@ -0,0 +1,223 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { + +enum ENUMPollingCommand { + POLLING_QPIRI = 0, + POLLING_QPIGS = 1, + POLLING_QMOD = 2, + POLLING_QFLAG = 3, + POLLING_QPIWS = 4, + POLLING_QT = 5, + POLLING_QMN = 6, +}; +struct PollingCommand { + uint8_t *command; + uint8_t length = 0; + uint8_t errors; + ENUMPollingCommand identifier; +}; + +#define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \ + protected: \ + value_type value_##name##_; \ + PIPSOLAR_ENTITY_(type, name, polling_command) + +#define PIPSOLAR_ENTITY_(type, name, polling_command) \ + protected: \ + type *name##_{}; /* NOLINT */ \ +\ + public: \ + void set_##name(type *name) { /* NOLINT */ \ + this->name##_ = name; \ + this->add_polling_command_(#polling_command, POLLING_##polling_command); \ + } + +#define PIPSOLAR_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(sensor::Sensor, name, polling_command, value_type) +#define PIPSOLAR_SWITCH(name, polling_command) PIPSOLAR_ENTITY_(switch_::Switch, name, polling_command) +#define PIPSOLAR_BINARY_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(binary_sensor::BinarySensor, name, polling_command, value_type) +#define PIPSOLAR_VALUED_TEXT_SENSOR(name, polling_command, value_type) \ + PIPSOLAR_VALUED_ENTITY_(text_sensor::TextSensor, name, polling_command, value_type) +#define PIPSOLAR_TEXT_SENSOR(name, polling_command) PIPSOLAR_ENTITY_(text_sensor::TextSensor, name, polling_command) + +class Pipsolar : public uart::UARTDevice, public PollingComponent { + // QPIGS values + PIPSOLAR_SENSOR(grid_voltage, QPIGS, float) + PIPSOLAR_SENSOR(grid_frequency, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_voltage, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_frequency, QPIGS, float) + PIPSOLAR_SENSOR(ac_output_apparent_power, QPIGS, int) + PIPSOLAR_SENSOR(ac_output_active_power, QPIGS, int) + PIPSOLAR_SENSOR(output_load_percent, QPIGS, int) + PIPSOLAR_SENSOR(bus_voltage, QPIGS, int) + PIPSOLAR_SENSOR(battery_voltage, QPIGS, float) + PIPSOLAR_SENSOR(battery_charging_current, QPIGS, int) + PIPSOLAR_SENSOR(battery_capacity_percent, QPIGS, int) + PIPSOLAR_SENSOR(inverter_heat_sink_temperature, QPIGS, int) + PIPSOLAR_SENSOR(pv_input_current_for_battery, QPIGS, int) + PIPSOLAR_SENSOR(pv_input_voltage, QPIGS, float) + PIPSOLAR_SENSOR(battery_voltage_scc, QPIGS, float) + PIPSOLAR_SENSOR(battery_discharge_current, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(add_sbu_priority_version, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(configuration_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(scc_firmware_version, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(load_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(battery_voltage_to_steady_while_charging, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(charging_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(scc_charging_status, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(ac_charging_status, QPIGS, int) + PIPSOLAR_SENSOR(battery_voltage_offset_for_fans_on, QPIGS, int) //.1 scale + PIPSOLAR_SENSOR(eeprom_version, QPIGS, int) + PIPSOLAR_SENSOR(pv_charging_power, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(charging_to_floating_mode, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(switch_on, QPIGS, int) + PIPSOLAR_BINARY_SENSOR(dustproof_installed, QPIGS, int) + + // QPIRI values + PIPSOLAR_SENSOR(grid_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(grid_rating_current, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_frequency, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_current, QPIRI, float) + PIPSOLAR_SENSOR(ac_output_rating_apparent_power, QPIRI, int) + PIPSOLAR_SENSOR(ac_output_rating_active_power, QPIRI, int) + PIPSOLAR_SENSOR(battery_rating_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_recharge_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_under_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_bulk_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_float_voltage, QPIRI, float) + PIPSOLAR_SENSOR(battery_type, QPIRI, int) + PIPSOLAR_SENSOR(current_max_ac_charging_current, QPIRI, int) + PIPSOLAR_SENSOR(current_max_charging_current, QPIRI, int) + PIPSOLAR_SENSOR(input_voltage_range, QPIRI, int) + PIPSOLAR_SENSOR(output_source_priority, QPIRI, int) + PIPSOLAR_SENSOR(charger_source_priority, QPIRI, int) + PIPSOLAR_SENSOR(parallel_max_num, QPIRI, int) + PIPSOLAR_SENSOR(machine_type, QPIRI, int) + PIPSOLAR_SENSOR(topology, QPIRI, int) + PIPSOLAR_SENSOR(output_mode, QPIRI, int) + PIPSOLAR_SENSOR(battery_redischarge_voltage, QPIRI, float) + PIPSOLAR_SENSOR(pv_ok_condition_for_parallel, QPIRI, int) + PIPSOLAR_SENSOR(pv_power_balance, QPIRI, int) + + // QMOD values + PIPSOLAR_VALUED_TEXT_SENSOR(device_mode, QMOD, char) + + // QFLAG values + PIPSOLAR_BINARY_SENSOR(silence_buzzer_open_buzzer, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(overload_bypass_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(lcd_escape_to_default, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(overload_restart_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(over_temperature_restart_function, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(backlight_on, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(alarm_on_when_primary_source_interrupt, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(fault_code_record, QFLAG, int) + PIPSOLAR_BINARY_SENSOR(power_saving, QFLAG, int) + + // QPIWS values + PIPSOLAR_BINARY_SENSOR(warnings_present, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(faults_present, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_power_loss, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_fault, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_over, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_under, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_bus_soft_fail, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_line_fail, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_opvshort, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_low, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_over_temperature, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_fan_lock, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_voltage_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_low_alarm, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_under_shutdown, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_derating, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_over_load, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_eeprom_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_over_current, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_inverter_soft_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_self_test_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_op_dc_voltage_over, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_battery_open, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_current_sensor_failed, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_battery_short, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_power_limit, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_pv_voltage_high, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_mppt_overload, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_mppt_overload, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_too_low_to_charge, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_dc_dc_over_current, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(fault_code, QPIWS, int) + PIPSOLAR_BINARY_SENSOR(warnung_low_pv_energy, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_high_ac_input_during_bus_soft_start, QPIWS, bool) + PIPSOLAR_BINARY_SENSOR(warning_battery_equalization, QPIWS, bool) + + PIPSOLAR_TEXT_SENSOR(last_qpigs, QPIGS) + PIPSOLAR_TEXT_SENSOR(last_qpiri, QPIRI) + PIPSOLAR_TEXT_SENSOR(last_qmod, QMOD) + PIPSOLAR_TEXT_SENSOR(last_qflag, QFLAG) + PIPSOLAR_TEXT_SENSOR(last_qpiws, QPIWS) + PIPSOLAR_TEXT_SENSOR(last_qt, QT) + PIPSOLAR_TEXT_SENSOR(last_qmn, QMN) + + PIPSOLAR_SWITCH(output_source_priority_utility_switch, QPIRI) + PIPSOLAR_SWITCH(output_source_priority_solar_switch, QPIRI) + PIPSOLAR_SWITCH(output_source_priority_battery_switch, QPIRI) + PIPSOLAR_SWITCH(input_voltage_range_switch, QPIRI) + PIPSOLAR_SWITCH(pv_ok_condition_for_parallel_switch, QPIRI) + PIPSOLAR_SWITCH(pv_power_balance_switch, QPIRI) + + void switch_command(const std::string &command); + void setup() override; + void loop() override; + void dump_config() override; + void update() override; + + protected: + static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length + static const size_t COMMAND_QUEUE_LENGTH = 10; + static const size_t COMMAND_TIMEOUT = 5000; + uint32_t last_poll_ = 0; + void add_polling_command_(const char *command, ENUMPollingCommand polling_command); + void empty_uart_buffer_(); + uint8_t check_incoming_crc_(); + uint8_t check_incoming_length_(uint8_t length); + uint16_t calc_crc_(uint8_t *msg, int n); + uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data); + uint8_t send_next_command_(); + void send_next_poll_(); + void queue_command_(const char *command, byte length); + std::string command_queue_[COMMAND_QUEUE_LENGTH]; + uint8_t command_queue_position_ = 0; + uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH]; + size_t read_pos_{0}; + + uint32_t command_start_millis_ = 0; + uint8_t state_; + enum State { + STATE_IDLE = 0, + STATE_POLL = 1, + STATE_COMMAND = 2, + STATE_POLL_COMPLETE = 3, + STATE_COMMAND_COMPLETE = 4, + STATE_POLL_CHECKED = 5, + STATE_POLL_DECODED = 6, + }; + + uint8_t last_polling_command_ = 0; + PollingCommand used_polling_commands_[15]; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py new file mode 100644 index 0000000000..5e4dd6c40c --- /dev/null +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -0,0 +1,220 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_EMPTY, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_PERCENT, + UNIT_VOLT, + UNIT_EMPTY, + UNIT_VOLT_AMPS, + UNIT_WATT, + CONF_BUS_VOLTAGE, + CONF_BATTERY_VOLTAGE, +) +from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID + +DEPENDENCIES = ["uart"] + +# QPIRI sensors +CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage" +CONF_GRID_RATING_CURRENT = "grid_rating_current" +CONF_AC_OUTPUT_RATING_VOLTAGE = "ac_output_rating_voltage" +CONF_AC_OUTPUT_RATING_FREQUENCY = "ac_output_rating_frequency" +CONF_AC_OUTPUT_RATING_CURRENT = "ac_output_rating_current" +CONF_AC_OUTPUT_RATING_APPARENT_POWER = "ac_output_rating_apparent_power" +CONF_AC_OUTPUT_RATING_ACTIVE_POWER = "ac_output_rating_active_power" +CONF_BATTERY_RATING_VOLTAGE = "battery_rating_voltage" +CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage" +CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage" +CONF_BATTERY_BULK_VOLTAGE = "battery_bulk_voltage" +CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage" +CONF_BATTERY_TYPE = "battery_type" +CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current" +CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current" +CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range" +CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority" +CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority" +CONF_PARALLEL_MAX_NUM = "parallel_max_num" +CONF_MACHINE_TYPE = "machine_type" +CONF_TOPOLOGY = "topology" +CONF_OUTPUT_MODE = "output_mode" +CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage" +CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel" +CONF_PV_POWER_BALANCE = "pv_power_balance" + +CONF_GRID_VOLTAGE = "grid_voltage" +CONF_GRID_FREQUENCY = "grid_frequency" +CONF_AC_OUTPUT_VOLTAGE = "ac_output_voltage" +CONF_AC_OUTPUT_FREQUENCY = "ac_output_frequency" +CONF_AC_OUTPUT_APPARENT_POWER = "ac_output_apparent_power" +CONF_AC_OUTPUT_ACTIVE_POWER = "ac_output_active_power" +CONF_OUTPUT_LOAD_PERCENT = "output_load_percent" +CONF_BATTERY_CHARGING_CURRENT = "battery_charging_current" +CONF_BATTERY_CAPACITY_PERCENT = "battery_capacity_percent" +CONF_INVERTER_HEAT_SINK_TEMPERATURE = "inverter_heat_sink_temperature" +CONF_PV_INPUT_CURRENT_FOR_BATTERY = "pv_input_current_for_battery" +CONF_PV_INPUT_VOLTAGE = "pv_input_voltage" +CONF_BATTERY_VOLTAGE_SCC = "battery_voltage_scc" +CONF_BATTERY_DISCHARGE_CURRENT = "battery_discharge_current" +CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version" +CONF_CONFIGURATION_STATUS = "configuration_status" +CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version" +CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON = "battery_voltage_offset_for_fans_on" +CONF_EEPROM_VERSION = "eeprom_version" +CONF_PV_CHARGING_POWER = "pv_charging_power" + +TYPES = { + CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_GRID_RATING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_TYPE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PARALLEL_MAX_NUM: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_MACHINE_TYPE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY), + CONF_OUTPUT_MODE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_POWER_BALANCE: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_GRID_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_GRID_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema( + UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + ), + CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), + CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_BUS_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema( + UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema( + UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + ), + CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + ), + CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + ), + CONF_EEPROM_VERSION: sensor.sensor_schema( + UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + ), + CONF_PV_CHARGING_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + ), +} + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): schema for type, schema in TYPES.items()} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, _ in TYPES.items(): + if type in config: + conf = config[type] + sens = await sensor.new_sensor(conf) + cg.add(getattr(paren, f"set_{type}")(sens)) diff --git a/esphome/components/pipsolar/switch/__init__.py b/esphome/components/pipsolar/switch/__init__.py new file mode 100644 index 0000000000..5ff33b10ff --- /dev/null +++ b/esphome/components/pipsolar/switch/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ICON_POWER, +) +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns + +DEPENDENCIES = ["uart"] + +CONF_OUTPUT_SOURCE_PRIORITY_UTILITY = "output_source_priority_utility" +CONF_OUTPUT_SOURCE_PRIORITY_SOLAR = "output_source_priority_solar" +CONF_OUTPUT_SOURCE_PRIORITY_BATTERY = "output_source_priority_battery" +CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range" +CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel" +CONF_PV_POWER_BALANCE = "pv_power_balance" + +TYPES = { + CONF_OUTPUT_SOURCE_PRIORITY_UTILITY: ("POP00", None), + CONF_OUTPUT_SOURCE_PRIORITY_SOLAR: ("POP01", None), + CONF_OUTPUT_SOURCE_PRIORITY_BATTERY: ("POP02", None), + CONF_INPUT_VOLTAGE_RANGE: ("PGR01", "PGR00"), + CONF_PV_OK_CONDITION_FOR_PARALLEL: ("PPVOKC1", "PPVOKC0"), + CONF_PV_POWER_BALANCE: ("PSPB1", "PSPB0"), +} + +PipsolarSwitch = pipsolar_ns.class_("PipsolarSwitch", switch.Switch, cg.Component) + +PIPSWITCH_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(PipsolarSwitch), + cv.Optional(CONF_INVERTED): cv.invalid( + "Pipsolar switches do not support inverted mode!" + ), + cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + } +).extend(cv.COMPONENT_SCHEMA) + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + {cv.Optional(type): PIPSWITCH_SCHEMA for type in TYPES} +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type, (on, off) in TYPES.items(): + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await switch.register_switch(var, conf) + cg.add(getattr(paren, f"set_{type}_switch")(var)) + cg.add(var.set_parent(paren)) + cg.add(var.set_on_command(on)) + if off is not None: + cg.add(var.set_off_command(off)) diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.cpp b/esphome/components/pipsolar/switch/pipsolar_switch.cpp new file mode 100644 index 0000000000..7eaeac1c2d --- /dev/null +++ b/esphome/components/pipsolar/switch/pipsolar_switch.cpp @@ -0,0 +1,24 @@ +#include "pipsolar_switch.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.switch"; + +void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); } +void PipsolarSwitch::write_state(bool state) { + if (state) { + if (this->on_command_.length() > 0) { + this->parent_->switch_command(this->on_command_); + } + } else { + if (this->off_command_.length() > 0) { + this->parent_->switch_command(this->off_command_); + } + } +} + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.h b/esphome/components/pipsolar/switch/pipsolar_switch.h new file mode 100644 index 0000000000..3fe4c7dfa1 --- /dev/null +++ b/esphome/components/pipsolar/switch/pipsolar_switch.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { +class Pipsolar; +class PipsolarSwitch : public switch_::Switch, public Component { + public: + void set_parent(Pipsolar *parent) { this->parent_ = parent; }; + void set_on_command(std::string command) { this->on_command_ = std::move(command); }; + void set_off_command(std::string command) { this->off_command_ = std::move(command); }; + void dump_config() override; + + protected: + void write_state(bool state) override; + std::string on_command_; + std::string off_command_; + Pipsolar *parent_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/__init__.py b/esphome/components/pipsolar/text_sensor/__init__.py new file mode 100644 index 0000000000..fe6c4979f3 --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/__init__.py @@ -0,0 +1,52 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ID +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns + +DEPENDENCIES = ["uart"] + +CONF_DEVICE_MODE = "device_mode" +CONF_LAST_QPIGS = "last_qpigs" +CONF_LAST_QPIRI = "last_qpiri" +CONF_LAST_QMOD = "last_qmod" +CONF_LAST_QFLAG = "last_qflag" +CONF_LAST_QPIWS = "last_qpiws" +CONF_LAST_QT = "last_qt" +CONF_LAST_QMN = "last_qmn" + +PipsolarTextSensor = pipsolar_ns.class_( + "PipsolarTextSensor", text_sensor.TextSensor, cg.Component +) + +TYPES = [ + CONF_DEVICE_MODE, + CONF_LAST_QPIGS, + CONF_LAST_QPIRI, + CONF_LAST_QMOD, + CONF_LAST_QFLAG, + CONF_LAST_QPIWS, + CONF_LAST_QT, + CONF_LAST_QMN, +] + +CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( + { + cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend( + {cv.GenerateID(): cv.declare_id(PipsolarTextSensor)} + ) + for type in TYPES + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_PIPSOLAR_ID]) + + for type in TYPES: + if type in config: + conf = config[type] + var = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(var, conf) + await cg.register_component(var, conf) + cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp new file mode 100644 index 0000000000..ee1fe2d1d8 --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp @@ -0,0 +1,13 @@ +#include "pipsolar_textsensor.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace pipsolar { + +static const char *const TAG = "pipsolar.text_sensor"; + +void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); } + +} // namespace pipsolar +} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h new file mode 100644 index 0000000000..871f6d8dee --- /dev/null +++ b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../pipsolar.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace pipsolar { +class Pipsolar; +class PipsolarTextSensor : public Component, public text_sensor::TextSensor { + public: + void set_parent(Pipsolar *parent) { this->parent_ = parent; }; + void dump_config() override; + + protected: + Pipsolar *parent_; +}; + +} // namespace pipsolar +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 2857e8ca5f..e52e8cc33c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -56,6 +56,9 @@ time: tuya: time_id: sntp_time +pipsolar: + id: inverter0 + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -63,6 +66,140 @@ sensor: - platform: tuya id: tuya_sensor sensor_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + grid_rating_voltage: + id: inverter0_grid_rating_voltage + name: inverter0_grid_rating_voltage + grid_rating_current: + id: inverter0_grid_rating_current + name: inverter0_grid_rating_current + ac_output_rating_voltage: + id: inverter0_ac_output_rating_voltage + name: inverter0_ac_output_rating_voltage + ac_output_rating_frequency: + id: inverter0_ac_output_rating_frequency + name: inverter0_ac_output_rating_frequency + ac_output_rating_current: + id: inverter0_ac_output_rating_current + name: inverter0_ac_output_rating_current + ac_output_rating_apparent_power: + id: inverter0_ac_output_rating_apparent_power + name: inverter0_ac_output_rating_apparent_power + ac_output_rating_active_power: + id: inverter0_ac_output_rating_active_power + name: inverter0_ac_output_rating_active_power + battery_rating_voltage: + id: inverter0_battery_rating_voltage + name: inverter0_battery_rating_voltage + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage + name: inverter0_battery_recharge_voltage + battery_under_voltage: + id: inverter0_battery_under_voltage + name: inverter0_battery_under_voltage + battery_bulk_voltage: + id: inverter0_battery_bulk_voltage + name: inverter0_battery_bulk_voltage + battery_float_voltage: + id: inverter0_battery_float_voltage + name: inverter0_battery_float_voltage + battery_type: + id: inverter0_battery_type + name: inverter0_battery_type + current_max_ac_charging_current: + id: inverter0_current_max_ac_charging_current + name: inverter0_current_max_ac_charging_current + current_max_charging_current: + id: inverter0_current_max_charging_current + name: inverter0_current_max_charging_current + input_voltage_range: + id: inverter0_input_voltage_range + name: inverter0_input_voltage_range + output_source_priority: + id: inverter0_output_source_priority + name: inverter0_output_source_priority + charger_source_priority: + id: inverter0_charger_source_priority + name: inverter0_charger_source_priority + parallel_max_num: + id: inverter0_parallel_max_num + name: inverter0_parallel_max_num + machine_type: + id: inverter0_machine_type + name: inverter0_machine_type + topology: + id: inverter0_topology + name: inverter0_topology + output_mode: + id: inverter0_output_mode + name: inverter0_output_mode + battery_redischarge_voltage: + id: inverter0_battery_redischarge_voltage + name: inverter0_battery_redischarge_voltage + pv_ok_condition_for_parallel: + id: inverter0_pv_ok_condition_for_parallel + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + id: inverter0_pv_power_balance + name: inverter0_pv_power_balance + grid_voltage: + id: inverter0_grid_voltage + name: inverter0_grid_voltage + grid_frequency: + id: inverter0_grid_frequency + name: inverter0_grid_frequency + ac_output_voltage: + id: inverter0_ac_output_voltage + name: inverter0_ac_output_voltage + ac_output_frequency: + id: inverter0_ac_output_frequency + name: inverter0_ac_output_frequency + ac_output_apparent_power: + id: inverter0_ac_output_apparent_power + name: inverter0_ac_output_apparent_power + ac_output_active_power: + id: inverter0_ac_output_active_power + name: inverter0_ac_output_active_power + output_load_percent: + id: inverter0_output_load_percent + name: inverter0_output_load_percent + bus_voltage: + id: inverter0_bus_voltage + name: inverter0_bus_voltage + battery_voltage: + id: inverter0_battery_voltage + name: inverter0_battery_voltage + battery_charging_current: + id: inverter0_battery_charging_current + name: inverter0_battery_charging_current + battery_capacity_percent: + id: inverter0_battery_capacity_percent + name: inverter0_battery_capacity_percent + inverter_heat_sink_temperature: + id: inverter0_inverter_heat_sink_temperature + name: inverter0_inverter_heat_sink_temperature + pv_input_current_for_battery: + id: inverter0_pv_input_current_for_battery + name: inverter0_pv_input_current_for_battery + pv_input_voltage: + id: inverter0_pv_input_voltage + name: inverter0_pv_input_voltage + battery_voltage_scc: + id: inverter0_battery_voltage_scc + name: inverter0_battery_voltage_scc + battery_discharge_current: + id: inverter0_battery_discharge_current + name: inverter0_battery_discharge_current + battery_voltage_offset_for_fans_on: + id: inverter0_battery_voltage_offset_for_fans_on + name: inverter0_battery_voltage_offset_for_fans_on + eeprom_version: + id: inverter0_eeprom_version + name: inverter0_eeprom_version + pv_charging_power: + id: inverter0_pv_charging_power + name: inverter0_pv_charging_power - platform: "hrxl_maxsonar_wr" name: "Rainwater Tank Level" filters: @@ -95,6 +232,59 @@ binary_sensor: - platform: tuya id: tuya_binary_sensor sensor_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + add_sbu_priority_version: + id: inverter0_add_sbu_priority_version + name: inverter0_add_sbu_priority_version + configuration_status: + id: inverter0_configuration_status + name: inverter0_configuration_status + scc_firmware_version: + id: inverter0_scc_firmware_version + name: inverter0_scc_firmware_version + load_status: + id: inverter0_load_status + name: inverter0_load_status + battery_voltage_to_steady_while_charging: + id: inverter0_battery_voltage_to_steady_while_charging + name: inverter0_battery_voltage_to_steady_while_charging + charging_status: + id: inverter0_charging_status + name: inverter0_charging_status + scc_charging_status: + id: inverter0_scc_charging_status + name: inverter0_scc_charging_status + ac_charging_status: + id: inverter0_ac_charging_status + name: inverter0_ac_charging_status + charging_to_floating_mode: + id: inverter0_charging_to_floating_mode + name: inverter0_charging_to_floating_mode + switch_on: + id: inverter0_switch_on + name: inverter0_switch_on + dustproof_installed: + id: inverter0_dustproof_installed + name: inverter0_dustproof_installed + silence_buzzer_open_buzzer: + id: inverter0_silence_buzzer_open_buzzer + name: inverter0_silence_buzzer_open_buzzer + overload_bypass_function: + id: inverter0_overload_bypass_function + name: inverter0_overload_bypass_function + lcd_escape_to_default: + id: inverter0_lcd_escape_to_default + name: inverter0_lcd_escape_to_default + overload_restart_function: + id: inverter0_overload_restart_function + name: inverter0_overload_restart_function + over_temperature_restart_function: + id: inverter0_over_temperature_restart_function + name: inverter0_over_temperature_restart_function + backlight_on: + id: inverter0_backlight_on + name: inverter0_backlight_on - platform: template id: ar1 lambda: 'return {};' @@ -131,6 +321,20 @@ switch: - platform: tuya id: tuya_switch switch_datapoint: 1 + - platform: pipsolar + pipsolar_id: inverter0 + output_source_priority_utility: + name: inverter0_output_source_priority_utility + output_source_priority_solar: + name: inverter0_output_source_priority_solar + output_source_priority_battery: + name: inverter0_output_source_priority_battery + input_voltage_range: + name: inverter0_input_voltage_range + pv_ok_condition_for_parallel: + name: inverter0_pv_ok_condition_for_parallel + pv_power_balance: + name: inverter0_pv_power_balance light: - platform: fastled_clockless @@ -204,6 +408,30 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); +text_sensor: + - platform: pipsolar + pipsolar_id: inverter0 + device_mode: + id: inverter0_device_mode + name: inverter0_device_mode + last_qpigs: + id: inverter0_last_qpigs + name: inverter0_last_qpigs + last_qpiri: + id: inverter0_last_qpiri + name: inverter0_last_qpiri + last_qmod: + id: inverter0_last_qmod + name: inverter0_last_qmod + last_qflag: + id: inverter0_last_qflag + name: inverter0_last_qflag + +output: + - platform: pipsolar + pipsolar_id: inverter0 + battery_recharge_voltage: + id: inverter0_battery_recharge_voltage_out esp32_camera: name: ESP-32 Camera data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19] From e5366dbbe73ce69e68e2b2c545bfa26302c79c53 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 10 Aug 2021 21:55:36 +0200 Subject: [PATCH 092/285] Add deassert_rts_dtr option to force RTS/DTR low when using miniterm (#2089) --- esphome/__main__.py | 14 +++++++++++++- esphome/components/logger/__init__.py | 2 ++ esphome/const.py | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 614bc12bae..b4770914a7 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -11,6 +11,7 @@ from esphome.config import iter_components, read_config, strip_default_ids from esphome.const import ( CONF_BAUD_RATE, CONF_BROKER, + CONF_DEASSERT_RTS_DTR, CONF_LOGGER, CONF_OTA, CONF_PASSWORD, @@ -99,10 +100,21 @@ def run_miniterm(config, port): baud_rate = config["logger"][CONF_BAUD_RATE] if baud_rate == 0: _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") + return _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) backtrace_state = False - with serial.Serial(port, baudrate=baud_rate) as ser: + ser = serial.Serial() + ser.baudrate = baud_rate + ser.port = port + + # We can't set to False by default since it leads to toggling and hence + # ESP32 resets on some platforms. + if config["logger"][CONF_DEASSERT_RTS_DTR]: + ser.dtr = False + ser.rts = False + + with ser: while True: try: raw = ser.readline() diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 8d79c96f63..55178941ec 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -7,6 +7,7 @@ from esphome.automation import LambdaAction from esphome.const import ( CONF_ARGS, CONF_BAUD_RATE, + CONF_DEASSERT_RTS_DTR, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, @@ -104,6 +105,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(Logger), cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, + cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection, cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, cv.Optional(CONF_LOGS, default={}): cv.Schema( diff --git a/esphome/const.py b/esphome/const.py index 9f3ce13b46..cd095677b3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -163,6 +163,7 @@ CONF_DATA_TEMPLATE = "data_template" CONF_DAYS_OF_MONTH = "days_of_month" CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" +CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" From 947c104eff56ae8ed5e242f1276cebc81aeb98c2 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Wed, 11 Aug 2021 14:07:10 +1000 Subject: [PATCH 093/285] Support for AM43 BLE blind motors (#1744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ben Buxton Co-authored-by: Otto Winter Co-authored-by: Stefan Agner Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: René Klomp Co-authored-by: Oxan van Leeuwen Co-authored-by: Geoff Davis Co-authored-by: Paulus Schoutsen Co-authored-by: dentra Co-authored-by: Guillermo Ruffino Co-authored-by: Franck Nijhof Co-authored-by: Barry Loong Co-authored-by: Sergey V. DUDANOV Co-authored-by: Balazs Scheidler --- CODEOWNERS | 2 + esphome/components/am43/__init__.py | 0 esphome/components/am43/am43.cpp | 115 ++++++++++++++ esphome/components/am43/am43.h | 45 ++++++ esphome/components/am43/am43_base.cpp | 142 ++++++++++++++++++ esphome/components/am43/am43_base.h | 78 ++++++++++ esphome/components/am43/cover/__init__.py | 36 +++++ esphome/components/am43/cover/am43_cover.cpp | 149 +++++++++++++++++++ esphome/components/am43/cover/am43_cover.h | 45 ++++++ esphome/components/am43/sensor.py | 46 ++++++ esphome/components/ble_client/ble_client.h | 2 +- tests/test1.yaml | 4 + 12 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 esphome/components/am43/__init__.py create mode 100644 esphome/components/am43/am43.cpp create mode 100644 esphome/components/am43/am43.h create mode 100644 esphome/components/am43/am43_base.cpp create mode 100644 esphome/components/am43/am43_base.h create mode 100644 esphome/components/am43/cover/__init__.py create mode 100644 esphome/components/am43/cover/am43_cover.cpp create mode 100644 esphome/components/am43/cover/am43_cover.h create mode 100644 esphome/components/am43/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 605222df63..1298d4d43d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,8 @@ esphome/core/* @esphome/core esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter +esphome/components/am43/* @buxtronix +esphome/components/am43/cover/* @buxtronix esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter diff --git a/esphome/components/am43/__init__.py b/esphome/components/am43/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp new file mode 100644 index 0000000000..e60bb2474c --- /dev/null +++ b/esphome/components/am43/am43.cpp @@ -0,0 +1,115 @@ +#include "am43.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace am43 { + +static const char *TAG = "am43"; + +void Am43::dump_config() { + ESP_LOGCONFIG(TAG, "AM43"); + LOG_SENSOR(" ", "Battery", this->battery_); + LOG_SENSOR(" ", "Illuminance", this->illuminance_); +} + +void Am43::setup() { + this->encoder_ = new Am43Encoder(); + this->decoder_ = new Am43Decoder(); + this->logged_in_ = false; + this->last_battery_update_ = 0; + this->current_sensor_ = 0; +} + +void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + this->logged_in_ = false; + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + this->logged_in_ = false; + this->node_state = espbt::ClientState::Idle; + if (this->battery_ != nullptr) + this->battery_->publish_state(NAN); + if (this->illuminance_ != nullptr) + this->illuminance_->publish_state(NAN); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + if (chr == nullptr) { + if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { + ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", + this->parent_->address_str().c_str()); + } else { + ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", + this->parent_->address_str().c_str()); + } + break; + } + this->char_handle_ = chr->handle; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::Established; + this->update(); + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_) + break; + this->decoder_->decode(param->notify.value, param->notify.value_len); + + if (this->battery_ != nullptr && this->decoder_->has_battery_level() && + millis() - this->last_battery_update_ > 10000) { + this->battery_->publish_state(this->decoder_->battery_level_); + this->last_battery_update_ = millis(); + } + + if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) { + this->illuminance_->publish_state(this->decoder_->light_level_); + } + + if (this->current_sensor_ > 0) { + if (this->illuminance_ != nullptr) { + auto packet = this->encoder_->get_light_level_request(); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), + status); + } + this->current_sensor_ = 0; + } + break; + } + default: + break; + } +} + +void Am43::update() { + if (this->node_state != espbt::ClientState::Established) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); + return; + } + if (this->current_sensor_ == 0) { + if (this->battery_ != nullptr) { + auto packet = this->encoder_->get_battery_level_request(); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } + this->current_sensor_++; + } +} + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h new file mode 100644 index 0000000000..460bb601b2 --- /dev/null +++ b/esphome/components/am43/am43.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/am43/am43_base.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include + +namespace esphome { +namespace am43 { + +namespace espbt = esphome::esp32_ble_tracker; + +class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent { + public: + void setup() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_battery(sensor::Sensor *battery) { battery_ = battery; } + void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } + + protected: + uint16_t char_handle_; + Am43Encoder *encoder_; + Am43Decoder *decoder_; + bool logged_in_; + sensor::Sensor *battery_{nullptr}; + sensor::Sensor *illuminance_{nullptr}; + uint8_t current_sensor_; + // The AM43 often gets into a state where it spams loads of battery update + // notifications. Here we will limit to no more than every 10s. + uint8_t last_battery_update_; +}; + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp new file mode 100644 index 0000000000..a17df55571 --- /dev/null +++ b/esphome/components/am43/am43_base.cpp @@ -0,0 +1,142 @@ +#include "am43_base.h" + +namespace esphome { +namespace am43 { + +const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a}; + +std::string pkt_to_hex(const uint8_t *data, uint16_t len) { + char buf[64]; + memset(buf, 0, 64); + for (int i = 0; i < len; i++) + sprintf(&buf[i * 2], "%02x", data[i]); + std::string ret = buf; + return ret; +} + +Am43Packet *Am43Encoder::get_battery_level_request() { + uint8_t data = 0x1; + return this->encode_(0xA2, &data, 1); +} + +Am43Packet *Am43Encoder::get_light_level_request() { + uint8_t data = 0x1; + return this->encode_(0xAA, &data, 1); +} + +Am43Packet *Am43Encoder::get_position_request() { + uint8_t data = 0x1; + return this->encode_(CMD_GET_POSITION, &data, 1); +} + +Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) { + uint8_t data[2]; + data[0] = (pin & 0xFF00) >> 8; + data[1] = pin & 0xFF; + return this->encode_(CMD_SEND_PIN, data, 2); +} + +Am43Packet *Am43Encoder::get_open_request() { + uint8_t data = 0xDD; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_close_request() { + uint8_t data = 0xEE; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_stop_request() { + uint8_t data = 0xCC; + return this->encode_(CMD_SET_STATE, &data, 1); +} + +Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) { + return this->encode_(CMD_SET_POSITION, &position, 1); +} + +void Am43Encoder::checksum_() { + uint8_t checksum = 0; + int i = 0; + for (i = 0; i < this->packet_.length; i++) + checksum = checksum ^ this->packet_.data[i]; + this->packet_.data[i] = checksum ^ 0xff; + this->packet_.length++; +} + +Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) { + memcpy(this->packet_.data, START_PACKET, 5); + this->packet_.data[5] = command; + this->packet_.data[6] = length; + memcpy(&this->packet_.data[7], data, length); + this->packet_.length = length + 7; + this->checksum_(); + ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str()); + return &this->packet_; +} + +#define VERIFY_MIN_LENGTH(x) \ + if (length < (x)) \ + return; + +void Am43Decoder::decode(const uint8_t *data, uint16_t length) { + this->has_battery_level_ = false; + this->has_light_level_ = false; + this->has_set_position_response_ = false; + this->has_set_state_response_ = false; + this->has_position_ = false; + this->has_pin_response_ = false; + ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str()); + + if (length < 2 || data[0] != 0x9a) + return; + switch (data[1]) { + case CMD_GET_BATTERY_LEVEL: { + VERIFY_MIN_LENGTH(8); + this->battery_level_ = data[7]; + this->has_battery_level_ = true; + break; + } + case CMD_GET_LIGHT_LEVEL: { + VERIFY_MIN_LENGTH(5); + this->light_level_ = 100 * ((float) data[4] / 9); + this->has_light_level_ = true; + break; + } + case CMD_GET_POSITION: { + VERIFY_MIN_LENGTH(6); + this->position_ = data[5]; + this->has_position_ = true; + break; + } + case CMD_NOTIFY_POSITION: { + VERIFY_MIN_LENGTH(5); + this->position_ = data[4]; + this->has_position_ = true; + break; + } + case CMD_SEND_PIN: { + VERIFY_MIN_LENGTH(4); + this->pin_ok_ = data[3] == RESPONSE_ACK; + this->has_pin_response_ = true; + break; + } + case CMD_SET_POSITION: { + VERIFY_MIN_LENGTH(4); + this->set_position_ok_ = data[3] == RESPONSE_ACK; + this->has_set_position_response_ = true; + break; + } + case CMD_SET_STATE: { + VERIFY_MIN_LENGTH(4); + this->set_state_ok_ = data[3] == RESPONSE_ACK; + this->has_set_state_response_ = true; + break; + } + default: + break; + } +}; + +} // namespace am43 +} // namespace esphome diff --git a/esphome/components/am43/am43_base.h b/esphome/components/am43/am43_base.h new file mode 100644 index 0000000000..e817f161fe --- /dev/null +++ b/esphome/components/am43/am43_base.h @@ -0,0 +1,78 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace am43 { + +static const uint16_t AM43_SERVICE_UUID = 0xFE50; +static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51; +// +// Tuya identifiers, only to detect and warn users as they are incompatible. +static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910; +static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11; + +struct Am43Packet { + uint8_t length; + uint8_t data[24]; +}; + +static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2; +static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA; +static const uint8_t CMD_GET_POSITION = 0xA7; +static const uint8_t CMD_SEND_PIN = 0x17; +static const uint8_t CMD_SET_STATE = 0x0A; +static const uint8_t CMD_SET_POSITION = 0x0D; +static const uint8_t CMD_NOTIFY_POSITION = 0xA1; + +static const uint8_t RESPONSE_ACK = 0x5A; +static const uint8_t RESPONSE_NACK = 0xA5; + +class Am43Encoder { + public: + Am43Packet *get_battery_level_request(); + Am43Packet *get_light_level_request(); + Am43Packet *get_position_request(); + Am43Packet *get_send_pin_request(uint16_t pin); + Am43Packet *get_open_request(); + Am43Packet *get_close_request(); + Am43Packet *get_stop_request(); + Am43Packet *get_set_position_request(uint8_t position); + + protected: + void checksum_(); + Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length); + Am43Packet packet_; +}; + +class Am43Decoder { + public: + void decode(const uint8_t *data, uint16_t length); + bool has_battery_level() { return this->has_battery_level_; } + bool has_light_level() { return this->has_light_level_; } + bool has_set_position_response() { return this->has_set_position_response_; } + bool has_set_state_response() { return this->has_set_state_response_; } + bool has_position() { return this->has_position_; } + bool has_pin_response() { return this->has_pin_response_; } + + union { + uint8_t position_; + uint8_t battery_level_; + float light_level_; + uint8_t set_position_ok_; + uint8_t set_state_ok_; + uint8_t pin_ok_; + }; + + protected: + bool has_battery_level_; + bool has_light_level_; + bool has_set_position_response_; + bool has_set_state_response_; + bool has_position_; + bool has_pin_response_; +}; + +} // namespace am43 +} // namespace esphome diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py new file mode 100644 index 0000000000..1ab0edbe78 --- /dev/null +++ b/esphome/components/am43/cover/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover, ble_client +from esphome.const import CONF_ID, CONF_PIN + +CODEOWNERS = ["@buxtronix"] +DEPENDENCIES = ["ble_client"] +AUTO_LOAD = ["am43"] + +CONF_INVERT_POSITION = "invert_position" + +am43_ns = cg.esphome_ns.namespace("am43") +Am43Component = am43_ns.class_( + "Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component +) + +CONFIG_SCHEMA = ( + cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Am43Component), + cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF), + cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + yield cg.register_component(var, config) + yield cover.register_cover(var, config) + yield ble_client.register_ble_node(var, config) diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp new file mode 100644 index 0000000000..3ae7fe8f8c --- /dev/null +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -0,0 +1,149 @@ +#include "am43_cover.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace am43 { + +static const char *TAG = "am43_cover"; + +using namespace esphome::cover; + +void Am43Component::dump_config() { + LOG_COVER("", "AM43 Cover", this); + ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_); + ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_); +} + +void Am43Component::setup() { + this->position = COVER_OPEN; + this->encoder_ = new Am43Encoder(); + this->decoder_ = new Am43Decoder(); + this->logged_in_ = false; +} + +void Am43Component::loop() { + if (this->node_state == espbt::ClientState::Established && !this->logged_in_) { + auto packet = this->encoder_->get_send_pin_request(this->pin_); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); + else + this->logged_in_ = true; + } +} + +CoverTraits Am43Component::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_position(true); + traits.set_supports_tilt(false); + traits.set_is_assumed_state(false); + return traits; +} + +void Am43Component::control(const CoverCall &call) { + if (this->node_state != espbt::ClientState::Established) { + ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str()); + return; + } + if (call.get_stop()) { + auto packet = this->encoder_->get_stop_request(); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + + if (this->invert_position_) + pos = 1 - pos; + auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); + auto status = + esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, + packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status); + } +} + +void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_DISCONNECT_EVT: { + this->logged_in_ = false; + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + if (chr == nullptr) { + if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { + ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str()); + } else { + ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str()); + } + break; + } + this->char_handle_ = chr->handle; + + auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status); + } + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::Established; + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle != this->char_handle_) + break; + this->decoder_->decode(param->notify.value, param->notify.value_len); + + if (this->decoder_->has_position()) { + this->position = ((float) this->decoder_->position_ / 100.0); + if (!this->invert_position_) + this->position = 1 - this->position; + if (this->position > 0.97) + this->position = 1.0; + if (this->position < 0.02) + this->position = 0.0; + this->publish_state(); + } + + if (this->decoder_->has_pin_response()) { + if (this->decoder_->pin_ok_) { + ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); + auto packet = this->encoder_->get_position_request(); + auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, + packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status); + } else { + ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str()); + } + } + + if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_) + ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str()); + + if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_) + ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str()); + break; + } + default: + break; + } +} + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h new file mode 100644 index 0000000000..5557da49e7 --- /dev/null +++ b/esphome/components/am43/cover/am43_cover.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/cover/cover.h" +#include "esphome/components/am43/am43_base.h" + +#ifdef ARDUINO_ARCH_ESP32 + +#include + +namespace esphome { +namespace am43 { + +namespace espbt = esphome::esp32_ble_tracker; + +class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component { + public: + void setup() override; + void loop() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + cover::CoverTraits get_traits() override; + void set_pin(uint16_t pin) { this->pin_ = pin; } + void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; } + + protected: + void control(const cover::CoverCall &call) override; + uint16_t char_handle_; + uint16_t pin_; + bool invert_position_; + Am43Encoder *encoder_; + Am43Decoder *decoder_; + bool logged_in_; + + float position_; +}; + +} // namespace am43 +} // namespace esphome + +#endif diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py new file mode 100644 index 0000000000..c88e529a0c --- /dev/null +++ b/esphome/components/am43/sensor.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client +from esphome.const import ( + CONF_ID, + CONF_BATTERY_LEVEL, + ICON_BATTERY, + CONF_ILLUMINANCE, + ICON_BRIGHTNESS_5, + UNIT_PERCENT, +) + +CODEOWNERS = ["@buxtronix"] + +am43_ns = cg.esphome_ns.namespace("am43") +Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Am43), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + UNIT_PERCENT, ICON_BATTERY, 0 + ), + cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( + UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 + ), + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.polling_component_schema("120s")) +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield ble_client.register_ble_node(var, config) + + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery(sens)) + + if CONF_ILLUMINANCE in config: + sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 203acc181f..b9d16ddacd 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -25,7 +25,7 @@ class BLEClientNode { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) = 0; - virtual void loop() = 0; + virtual void loop(){}; void set_address(uint64_t address) { address_ = address; } espbt::ESPBTClient *client; // This should be transitioned to Established once the node no longer needs diff --git a/tests/test1.yaml b/tests/test1.yaml index 9b7139de3a..bcf5f932a8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2145,6 +2145,10 @@ cover: id: template_cover state: CLOSED assumed_state: no + - platform: am43 + name: 'Test AM43' + id: am43_test + ble_client_id: ble_foo debug: From 11477dbc033ffb2603c6acd3062f46a267c1531a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 11 Aug 2021 06:50:05 +0200 Subject: [PATCH 094/285] Fix format warning in Tuya component (#1954) --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index bbab4f04ce..e42c74005e 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -297,7 +297,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); break; default: - ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, datapoint.type); + ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); return; } From 46f17bea66855f5ca0e96815ac5ca587f320de39 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 11 Aug 2021 06:51:35 +0200 Subject: [PATCH 095/285] Modular light transformers (#2124) --- .../components/light/addressable_light.cpp | 91 ++++++++------- esphome/components/light/addressable_light.h | 16 +++ esphome/components/light/light_output.cpp | 12 ++ esphome/components/light/light_output.h | 4 + esphome/components/light/light_state.cpp | 38 +++---- esphome/components/light/light_state.h | 3 - esphome/components/light/light_transformer.h | 106 ++++-------------- esphome/components/light/transformers.h | 75 +++++++++++++ 8 files changed, 195 insertions(+), 150 deletions(-) create mode 100644 esphome/components/light/light_output.cpp create mode 100644 esphome/components/light/transformers.h diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 4eb9124a7b..12eab6a685 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -24,6 +24,10 @@ void AddressableLight::call_setup() { #endif } +std::unique_ptr AddressableLight::create_default_transition() { + return make_unique(*this); +} + Color esp_color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); @@ -37,66 +41,67 @@ void AddressableLight::write_state(LightState *state) { auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state()); this->correction_.set_local_brightness(max_brightness); - this->last_transition_progress_ = 0.0f; - this->accumulated_alpha_ = 0.0f; - if (this->is_effect_active()) return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView + this->all() = esp_color_from_light_color_values(val); +} - if (state->transformer_ == nullptr || !state->transformer_->is_transition()) { - // no transformer active or non-transition one - this->all() = esp_color_from_light_color_values(val); - } else { - // transition transformer active, activate specialized transition for addressable effects - // instead of using a unified transition for all LEDs, we use the current state each LED as the - // start. Warning: ugly +void AddressableLightTransformer::start() { + auto end_values = this->target_values_; + this->target_color_ = esp_color_from_light_color_values(end_values); - // We can't use a direct lerp smoothing here though - that would require creating a copy of the original - // state of each LED at the start of the transition - // Instead, we "fake" the look of the LERP by using an exponential average over time and using - // dynamically-calculated alpha values to match the look of the + // our transition will handle brightness, disable brightness in correction. + this->light_.correction_.set_local_brightness(255); + this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); +} - float new_progress = state->transformer_->get_progress(); - float prev_smoothed = LightTransitionTransformer::smoothed_progress(last_transition_progress_); - float new_smoothed = LightTransitionTransformer::smoothed_progress(new_progress); - this->last_transition_progress_ = new_progress; +optional AddressableLightTransformer::apply() { + // Don't try to transition over running effects, instead immediately use the target values. write_state() and the + // effects pick up the change from current_values. + if (this->light_.is_effect_active()) + return this->target_values_; - auto end_values = state->transformer_->get_end_values(); - Color target_color = esp_color_from_light_color_values(end_values); + // Use a specialized transition for addressable lights: instead of using a unified transition for + // all LEDs, we use the current state of each LED as the start. - // our transition will handle brightness, disable brightness in correction. - this->correction_.set_local_brightness(255); - target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state()); + // We can't use a direct lerp smoothing here though - that would require creating a copy of the original + // state of each LED at the start of the transition. + // Instead, we "fake" the look of the LERP by using an exponential average over time and using + // dynamically-calculated alpha values to match the look. - float denom = (1.0f - new_smoothed); - float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length - // We solve this by accumulating the fractional part of the alpha over time. - float alpha255 = alpha * 255.0f; - float alpha255int = floorf(alpha255); - float alpha255remainder = alpha255 - alpha255int; + float denom = (1.0f - smoothed_progress); + float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; - this->accumulated_alpha_ += alpha255remainder; - float alpha_add = floorf(this->accumulated_alpha_); - this->accumulated_alpha_ -= alpha_add; + // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length + // We solve this by accumulating the fractional part of the alpha over time. + float alpha255 = alpha * 255.0f; + float alpha255int = floorf(alpha255); + float alpha255remainder = alpha255 - alpha255int; - alpha255 += alpha_add; - alpha255 = clamp(alpha255, 0.0f, 255.0f); - auto alpha8 = static_cast(alpha255); + this->accumulated_alpha_ += alpha255remainder; + float alpha_add = floorf(this->accumulated_alpha_); + this->accumulated_alpha_ -= alpha_add; - if (alpha8 != 0) { - uint8_t inv_alpha8 = 255 - alpha8; - Color add = target_color * alpha8; + alpha255 += alpha_add; + alpha255 = clamp(alpha255, 0.0f, 255.0f); + auto alpha8 = static_cast(alpha255); - for (auto led : *this) - led = add + led.get() * inv_alpha8; - } + if (alpha8 != 0) { + uint8_t inv_alpha8 = 255 - alpha8; + Color add = this->target_color_ * alpha8; + + for (auto led : this->light_) + led.set(add + led.get() * inv_alpha8); } - this->schedule_show(); + this->last_transition_progress_ = smoothed_progress; + this->light_.schedule_show(); + + return {}; } } // namespace light diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 54a1e3c6b4..ab1efdf160 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -8,6 +8,7 @@ #include "esp_range_view.h" #include "light_output.h" #include "light_state.h" +#include "transformers.h" #ifdef USE_POWER_SUPPLY #include "esphome/components/power_supply/power_supply.h" @@ -53,6 +54,7 @@ class AddressableLight : public LightOutput, public Component { bool is_effect_active() const { return this->effect_active_; } void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } void write_state(LightState *state) override; + std::unique_ptr create_default_transition() override; void set_correction(float red, float green, float blue, float white = 1.0f) { this->correction_.set_max_brightness( Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white))); @@ -70,6 +72,8 @@ class AddressableLight : public LightOutput, public Component { void call_setup() override; protected: + friend class AddressableLightTransformer; + bool should_show_() const { return this->effect_active_ || this->next_show_; } void mark_shown_() { this->next_show_ = false; @@ -92,6 +96,18 @@ class AddressableLight : public LightOutput, public Component { power_supply::PowerSupplyRequester power_; #endif LightState *state_parent_{nullptr}; +}; + +class AddressableLightTransformer : public LightTransitionTransformer { + public: + AddressableLightTransformer(AddressableLight &light) : light_(light) {} + + void start() override; + optional apply() override; + + protected: + AddressableLight &light_; + Color target_color_{}; float last_transition_progress_{0.0f}; float accumulated_alpha_{0.0f}; }; diff --git a/esphome/components/light/light_output.cpp b/esphome/components/light/light_output.cpp new file mode 100644 index 0000000000..e805a0b694 --- /dev/null +++ b/esphome/components/light/light_output.cpp @@ -0,0 +1,12 @@ +#include "light_output.h" +#include "transformers.h" + +namespace esphome { +namespace light { + +std::unique_ptr LightOutput::create_default_transition() { + return make_unique(); +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index d05cbf9436..7568ea6831 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "light_traits.h" #include "light_state.h" +#include "light_transformer.h" namespace esphome { namespace light { @@ -13,6 +14,9 @@ class LightOutput { /// Return the LightTraits of this LightOutput. virtual LightTraits get_traits() = 0; + /// Return the default transformer used for transitions. + virtual std::unique_ptr create_default_transition(); + virtual void setup_state(LightState *state) {} virtual void write_state(LightState *state) = 0; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 3c4a10c88e..278229fbd1 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,6 +1,7 @@ -#include "light_state.h" #include "esphome/core/log.h" +#include "light_state.h" #include "light_output.h" +#include "transformers.h" namespace esphome { namespace light { @@ -105,19 +106,19 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { + auto values = this->transformer_->apply(); + this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to + if (values.has_value()) + this->current_values = *values; + if (this->transformer_->is_finished()) { - this->remote_values = this->current_values = this->transformer_->get_end_values(); - this->target_state_reached_callback_.call(); - if (this->transformer_->publish_at_end()) - this->publish_state(); + this->transformer_->stop(); this->transformer_ = nullptr; - } else { - this->current_values = this->transformer_->get_values(); - this->remote_values = this->transformer_->get_remote_values(); + this->target_state_reached_callback_.call(); } - this->next_write_ = true; } + // Write state to the light if (this->next_write_) { this->output_->write_state(this); this->next_write_ = false; @@ -217,18 +218,21 @@ void LightState::stop_effect_() { } void LightState::start_transition_(const LightColorValues &target, uint32_t length) { - this->transformer_ = make_unique(millis(), length, this->current_values, target); - this->remote_values = this->transformer_->get_remote_values(); + this->transformer_ = this->output_->create_default_transition(); + this->transformer_->setup(this->current_values, target, length); + this->remote_values = target; } void LightState::start_flash_(const LightColorValues &target, uint32_t length) { - LightColorValues end_colors = this->current_values; + LightColorValues end_colors = this->remote_values; // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works if (this->transformer_ != nullptr) - end_colors = this->transformer_->get_end_values(); - this->transformer_ = make_unique(millis(), length, end_colors, target); - this->remote_values = this->transformer_->get_remote_values(); + end_colors = this->transformer_->get_target_values(); + + this->transformer_ = make_unique(*this); + this->transformer_->setup(end_colors, target, length); + this->remote_values = target; } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { @@ -240,10 +244,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot this->next_write_ = true; } -void LightState::set_transformer_(std::unique_ptr transformer) { - this->transformer_ = std::move(transformer); -} - void LightState::save_remote_values_() { LightStateRTCState saved; saved.color_mode = this->remote_values.get_color_mode(); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 23527e8a47..dfea9a15f4 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -157,9 +157,6 @@ class LightState : public Nameable, public Component { /// Internal method to set the color values to target immediately (with no transition). void set_immediately_(const LightColorValues &target, bool set_remote_values); - /// Internal method to start a transformer. - void set_transformer_(std::unique_ptr transformer); - /// Internal method to save the current remote_values to the preferences void save_remote_values_(); diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index 7db6265a89..c5181abd4f 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -1,42 +1,42 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "light_color_values.h" namespace esphome { namespace light { -/// Base-class for all light color transformers, such as transitions or flashes. +/// Base class for all light color transformers, such as transitions or flashes. class LightTransformer { public: - LightTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : start_time_(start_time), length_(length), start_values_(start_values), target_values_(target_values) {} + void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) { + this->start_time_ = millis(); + this->length_ = length; + this->start_values_ = start_values; + this->target_values_ = target_values; + this->start(); + } - LightTransformer() = delete; + /// Indicates whether this transformation is finished. + virtual bool is_finished() { return this->get_progress_() >= 1.0f; } - /// Whether this transformation is finished - virtual bool is_finished() { return this->get_progress() >= 1.0f; } + /// This will be called before the transition is started. + virtual void start() {} - /// This will be called to get the current values for output. - virtual LightColorValues get_values() = 0; + /// This will be called while the transformer is active to apply the transition to the light. Can either write to the + /// light directly, or return LightColorValues that will be applied. + virtual optional apply() = 0; - /// The values that should be reported to the front-end. - virtual LightColorValues get_remote_values() { return this->get_target_values_(); } + /// This will be called after transition is finished. + virtual void stop() {} - /// The values that should be set after this transformation is complete. - virtual LightColorValues get_end_values() { return this->get_target_values_(); } + const LightColorValues &get_start_values() const { return this->start_values_; } - virtual bool publish_at_end() = 0; - virtual bool is_transition() = 0; - - float get_progress() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } + const LightColorValues &get_target_values() const { return this->target_values_; } protected: - const LightColorValues &get_start_values_() const { return this->start_values_; } - - const LightColorValues &get_target_values_() const { return this->target_values_; } + /// The progress of this transition, on a scale of 0 to 1. + float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } uint32_t start_time_; uint32_t length_; @@ -44,69 +44,5 @@ class LightTransformer { LightColorValues target_values_; }; -class LightTransitionTransformer : public LightTransformer { - public: - LightTransitionTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : LightTransformer(start_time, length, start_values, target_values) { - // When turning light on from off state, use colors from new. - if (!this->start_values_.is_on() && this->target_values_.is_on()) { - this->start_values_ = LightColorValues(target_values); - this->start_values_.set_brightness(0.0f); - } - - // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. - if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { - this->changing_color_mode_ = true; - this->intermediate_values_ = LightColorValues(this->get_start_values_()); - this->intermediate_values_.set_state(false); - } - } - - LightColorValues get_values() override { - float p = this->get_progress(); - - // Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off. - if (this->changing_color_mode_ && p > 0.5f && - this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) { - this->intermediate_values_ = LightColorValues(this->get_end_values()); - this->intermediate_values_.set_state(false); - } - - LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; - LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; - if (this->changing_color_mode_) - p = p < 0.5f ? p * 2 : (p - 0.5) * 2; - - float v = LightTransitionTransformer::smoothed_progress(p); - return LightColorValues::lerp(start, end, v); - } - - bool publish_at_end() override { return false; } - bool is_transition() override { return true; } - - // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like - // transition from 0 to 1 on x = [0, 1] - static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } - - protected: - bool changing_color_mode_{false}; - LightColorValues intermediate_values_{}; -}; - -class LightFlashTransformer : public LightTransformer { - public: - LightFlashTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values, - const LightColorValues &target_values) - : LightTransformer(start_time, length, start_values, target_values) {} - - LightColorValues get_values() override { return this->get_target_values_(); } - - LightColorValues get_end_values() override { return this->get_start_values_(); } - - bool publish_at_end() override { return true; } - bool is_transition() override { return false; } -}; - } // namespace light } // namespace esphome diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h new file mode 100644 index 0000000000..fd0bfd20f3 --- /dev/null +++ b/esphome/components/light/transformers.h @@ -0,0 +1,75 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "light_color_values.h" +#include "light_state.h" +#include "light_transformer.h" + +namespace esphome { +namespace light { + +class LightTransitionTransformer : public LightTransformer { + public: + void start() override { + // When turning light on from off state, use colors from target state. + if (!this->start_values_.is_on() && this->target_values_.is_on()) { + this->start_values_ = LightColorValues(this->target_values_); + this->start_values_.set_brightness(0.0f); + } + + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. + if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->changing_color_mode_ = true; + this->intermediate_values_ = this->start_values_; + this->intermediate_values_.set_state(false); + } + } + + optional apply() override { + float p = this->get_progress_(); + + // Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off. + if (this->changing_color_mode_ && p > 0.5f && + this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) { + this->intermediate_values_ = this->target_values_; + this->intermediate_values_.set_state(false); + } + + LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; + if (this->changing_color_mode_) + p = p < 0.5f ? p * 2 : (p - 0.5) * 2; + + float v = LightTransitionTransformer::smoothed_progress(p); + return LightColorValues::lerp(start, end, v); + } + + protected: + // This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like + // transition from 0 to 1 on x = [0, 1] + static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } + + bool changing_color_mode_{false}; + LightColorValues intermediate_values_{}; +}; + +class LightFlashTransformer : public LightTransformer { + public: + LightFlashTransformer(LightState &state) : state_(state) {} + + optional apply() override { return this->get_target_values(); } + + // Restore the original values after the flash. + void stop() override { + this->state_.current_values = this->get_start_values(); + this->state_.remote_values = this->get_start_values(); + this->state_.publish_state(); + } + + protected: + LightState &state_; +}; + +} // namespace light +} // namespace esphome From 8c41fc2b1dd22fb6e574d6982f88b32a60204b49 Mon Sep 17 00:00:00 2001 From: Branimir Lambov Date: Wed, 11 Aug 2021 06:14:17 +0100 Subject: [PATCH 096/285] Support for the DKE screen version of LilyGo-TTGO-T5 V2.3 (#1969) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/waveshare_epaper/display.py | 8 +- .../waveshare_epaper/waveshare_epaper.cpp | 131 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 28 ++++ 3 files changed, 165 insertions(+), 2 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 5a40b92ce2..e825456c36 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -43,6 +43,9 @@ WaveshareEPaper7P5In = waveshare_epaper_ns.class_( WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) +WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InDKE", WaveshareEPaper +) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") @@ -64,13 +67,14 @@ MODELS = { "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } def validate_full_update_every_only_type_a(value): if CONF_FULL_UPDATE_EVERY not in value: return value - if MODELS[value[CONF_MODEL]][0] != "a": + if MODELS[value[CONF_MODEL]][0] == "b": raise cv.Invalid( "The 'full_update_every' option is only available for models " "'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'." @@ -101,7 +105,7 @@ async def to_code(config): if model_type == "a": rhs = WaveshareEPaperTypeA.new(model) var = cg.Pvariable(config[CONF_ID], rhs, WaveshareEPaperTypeA) - elif model_type == "b": + elif model_type in ("b", "c"): rhs = model.new() var = cg.Pvariable(config[CONF_ID], rhs, model) else: diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 17d04b556b..c32e7d27a0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1080,5 +1080,136 @@ void WaveshareEPaper7P5InV2::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } + +static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; + +static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + // 0x22, 0x17, 0x41, 0x0, 0x32, 0x32 +}; + +void WaveshareEPaper2P13InDKE::initialize() {} +void HOT WaveshareEPaper2P13InDKE::display() { + bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + + if (partial) + ESP_LOGI(TAG, "Performing partial e-paper update."); + else + ESP_LOGI(TAG, "Performing full e-paper update."); + + // start and set up data format + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x11); + this->data(0x03); + this->command(0x44); + this->data(1); + this->data(this->get_width_internal() / 8); + this->command(0x45); + this->data(0); + this->data(0); + this->data(this->get_height_internal()); + this->data(0); + this->command(0x4e); + this->data(1); + this->command(0x4f); + this->data(0); + this->data(0); + + if (!partial) { + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit + this->command(0x20); + this->wait_until_idle_(); + } else { + // set up partial update + this->command(0x32); + for (uint8_t v : PART_UPDATE_LUT_TTGO_DKE) + this->data(v); + this->command(0x3F); + this->data(0x22); + + this->command(0x03); + this->data(0x17); + this->command(0x04); + this->data(0x41); + this->data(0x00); + this->data(0x32); + this->command(0x2C); + this->data(0x32); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + this->command(0x22); + this->data(0xC0); + this->command(0x20); + this->wait_until_idle_(); + + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit as partial + this->command(0x22); + this->data(0xCF); + this->command(0x20); + this->wait_until_idle_(); + + // data must be sent again on partial update + delay(300); // NOLINT + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(300); // NOLINT + } + + ESP_LOGI(TAG, "Completed e-paper update."); +} + +int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } +int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } +int WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +void WaveshareEPaper2P13InDKE::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P13InDKE::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + } // namespace waveshare_epaper } // namespace esphome diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 228753d1b7..f7603c5af0 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -301,5 +301,33 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper2P13InDKE : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + int idle_timeout_() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; +}; + } // namespace waveshare_epaper } // namespace esphome From d43640915364e1410d5c62d9132dc4d0c4a9ff9b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 11 Aug 2021 07:21:57 +0200 Subject: [PATCH 097/285] Support multiple configuration directories for update-all subcommand (#1925) --- esphome/__main__.py | 9 +++------ esphome/dashboard/dashboard.py | 2 +- esphome/util.py | 19 +++++++++++++------ esphome/vscode.py | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index b4770914a7..8d6f2b8f89 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -296,7 +296,6 @@ def command_vscode(args): logging.disable(logging.INFO) logging.disable(logging.WARNING) - CORE.config_path = args.configuration vscode.read_config(args) @@ -406,7 +405,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration[0]) + files = list_yaml_files(args.configuration) twidth = 60 def print_bar(middle_text): @@ -694,14 +693,12 @@ def parse_args(argv): ) parser_vscode = subparsers.add_parser("vscode") - parser_vscode.add_argument( - "configuration", help="Your YAML configuration file.", nargs=1 - ) + parser_vscode.add_argument("configuration", help="Your YAML configuration file.") parser_vscode.add_argument("--ace", action="store_true") parser_update = subparsers.add_parser("update-all") parser_update.add_argument( - "configuration", help="Your YAML configuration file directory.", nargs=1 + "configuration", help="Your YAML configuration file directories.", nargs="+" ) return parser.parse_args(argv[1:]) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index d10e5ff002..ea55ea3b18 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -98,7 +98,7 @@ class DashboardSettings: return os.path.join(self.config_dir, *args) def list_yaml_files(self): - return util.list_yaml_files(self.config_dir) + return util.list_yaml_files([self.config_dir]) settings = DashboardSettings() diff --git a/esphome/util.py b/esphome/util.py index 10f9923c44..56bc97ca71 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -247,17 +247,24 @@ class OrderedDict(collections.OrderedDict): return dict(self).__repr__() -def list_yaml_files(folder): - files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)]) +def list_yaml_files(folders): + files = filter_yaml_files( + [os.path.join(folder, p) for folder in folders for p in os.listdir(folder)] + ) files.sort() return files def filter_yaml_files(files): - files = [f for f in files if os.path.splitext(f)[1] == ".yaml"] - files = [f for f in files if os.path.basename(f) != "secrets.yaml"] - files = [f for f in files if not os.path.basename(f).startswith(".")] - return files + return [ + f + for f in files + if ( + os.path.splitext(f)[1] == ".yaml" + and os.path.basename(f) != "secrets.yaml" + and not os.path.basename(f).startswith(".") + ) + ] class SerialPort: diff --git a/esphome/vscode.py b/esphome/vscode.py index 6e1a0270be..68d59abd02 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -67,7 +67,7 @@ def read_config(args): CORE.ace = args.ace f = data["file"] if CORE.ace: - CORE.config_path = os.path.join(args.configuration[0], f) + CORE.config_path = os.path.join(args.configuration, f) else: CORE.config_path = data["file"] vs = VSCodeResult() From 5edebaf4686ceb30a4d0440b52d63811e9be6efd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 19:48:46 +1200 Subject: [PATCH 098/285] Bump version to v1.22.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index cd095677b3..50bac3ca8d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0-dev" +__version__ = "1.22.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From b0bc898278810d4cc8267ee03dc9df36a0476fa0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:27:39 +1200 Subject: [PATCH 099/285] Bump version to v1.21.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 50bac3ca8d..2b9418a81c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.22.0-dev" +__version__ = "1.21.0b1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 6847645782d1e1993fd9bc269d7ec9100d588d00 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:45:04 +1200 Subject: [PATCH 100/285] Fix bad merge conflict --- esphome/components/sensor/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ebd4fcc26c..26ddac3464 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,11 +258,11 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type_ != LAST_RESET_TYPE_NONE: + if last_reset_type != LAST_RESET_TYPE_NONE: schema = schema.extend( { cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type_ + CONF_LAST_RESET_TYPE, default=last_reset_type ): validate_last_reset_type } ) From 2735f96516ed94fbe10b44c79be21e82d7fbabfe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:54:24 +1200 Subject: [PATCH 101/285] Fix bad merge again --- esphome/components/sensor/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 26ddac3464..19f2a9f0e6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,14 +258,6 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type != LAST_RESET_TYPE_NONE: - schema = schema.extend( - { - cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type - ): validate_last_reset_type - } - ) return schema From 9173da04162beef7f89becf03107d3b24ad1f2f0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 15 Aug 2021 21:40:34 +0200 Subject: [PATCH 102/285] Always send all light state values in API (#2150) --- esphome/components/api/api_connection.cpp | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5fba549a57..f644ec62e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.key = light->get_object_id_hash(); resp.state = values.is_on(); resp.color_mode = static_cast(color_mode); - if (color_mode & light::ColorCapability::BRIGHTNESS) - resp.brightness = values.get_brightness(); - if (color_mode & light::ColorCapability::RGB) { - resp.color_brightness = values.get_color_brightness(); - resp.red = values.get_red(); - resp.green = values.get_green(); - resp.blue = values.get_blue(); - } - if (color_mode & light::ColorCapability::WHITE) - resp.white = values.get_white(); - if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) - resp.color_temperature = values.get_color_temperature(); - if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { - resp.cold_white = values.get_cold_white(); - resp.warm_white = values.get_warm_white(); - } + resp.brightness = values.get_brightness(); + resp.color_brightness = values.get_color_brightness(); + resp.red = values.get_red(); + resp.green = values.get_green(); + resp.blue = values.get_blue(); + resp.white = values.get_white(); + resp.color_temperature = values.get_color_temperature(); + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); From 303b6990057d271d43d72c8cf2017ad94e1ca443 Mon Sep 17 00:00:00 2001 From: puuu Date: Mon, 16 Aug 2021 04:59:29 +0900 Subject: [PATCH 103/285] let sensors announce its state_class via mqtt (#2155) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index c106a95902..ce7e89c584 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; + if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) + root["state_class"] = "measurement"; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { From 117b58ebe6c5a93e1f646df5964fe46e529fb027 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 15 Aug 2021 16:31:48 -0500 Subject: [PATCH 104/285] Thermostat delayed fan mode fix (#2158) --- .../thermostat/thermostat_climate.cpp | 24 +++++++++++-------- .../thermostat/thermostat_climate.h | 7 ++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d9d8b106ea..a75713cbb9 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -57,29 +57,34 @@ void ThermostatClimate::refresh() { } bool ThermostatClimate::climate_action_change_delayed() { + bool state_mismatch = this->action != this->compute_action_(true); + switch (this->compute_action_(true)) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - return !this->idle_action_ready_(); + return state_mismatch && (!this->idle_action_ready_()); case climate::CLIMATE_ACTION_COOLING: - return !this->cooling_action_ready_(); + return state_mismatch && (!this->cooling_action_ready_()); case climate::CLIMATE_ACTION_HEATING: - return !this->heating_action_ready_(); + return state_mismatch && (!this->heating_action_ready_()); case climate::CLIMATE_ACTION_FAN: - return !this->fanning_action_ready_(); + return state_mismatch && (!this->fanning_action_ready_()); case climate::CLIMATE_ACTION_DRYING: - return !this->drying_action_ready_(); + return state_mismatch && (!this->drying_action_ready_()); default: break; } return false; } -bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } +bool ThermostatClimate::fan_mode_change_delayed() { + bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_; + return state_mismatch && (!this->fan_mode_ready_()); +} climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } -climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } +climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; } bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && @@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - this->desired_fan_mode_ = fan_mode; // needed for timer callback + this->fan_mode = fan_mode; if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->start_timer_(thermostat::TIMER_FAN_MODE); assert(trig != nullptr); trig->trigger(); - this->fan_mode = fan_mode; this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); this->timer_[thermostat::TIMER_FAN_MODE].active = false; - this->switch_to_fan_mode_(this->desired_fan_mode_); + this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 1a5fd82ac0..60777e7c81 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool fan_mode_change_delayed(); /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) climate::ClimateAction delayed_climate_action(); - /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) - climate::ClimateFanMode delayed_fan_mode(); + /// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode locked_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; - /// Desired fan_mode -- used to store desired mode for callback when switching is delayed - climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; - /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called From 9b48ff5775750cc08c9fae2288b78e35da5a7db9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 16 Aug 2021 01:57:50 +0200 Subject: [PATCH 105/285] Fix native API log level enum values (#2151) --- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 11 ++--------- esphome/components/api/api_pb2.h | 8 ++++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b9b56d3f8e..d21e82f50c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -555,9 +555,10 @@ enum LogLevel { LOG_LEVEL_ERROR = 1; LOG_LEVEL_WARN = 2; LOG_LEVEL_INFO = 3; - LOG_LEVEL_DEBUG = 4; - LOG_LEVEL_VERBOSE = 5; - LOG_LEVEL_VERY_VERBOSE = 6; + LOG_LEVEL_CONFIG = 4; + LOG_LEVEL_DEBUG = 5; + LOG_LEVEL_VERBOSE = 6; + LOG_LEVEL_VERY_VERBOSE = 7; } message SubscribeLogsRequest { option (id) = 28; @@ -572,7 +573,6 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - string tag = 2; string message = 3; bool send_failed = 4; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f644ec62e0..4a31f15e77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -694,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin auto buffer = this->create_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); - // string tag = 2; - // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index eecba7a68e..aaeef57324 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -120,6 +120,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "LOG_LEVEL_WARN"; case enums::LOG_LEVEL_INFO: return "LOG_LEVEL_INFO"; + case enums::LOG_LEVEL_CONFIG: + return "LOG_LEVEL_CONFIG"; case enums::LOG_LEVEL_DEBUG: return "LOG_LEVEL_DEBUG"; case enums::LOG_LEVEL_VERBOSE: @@ -2334,10 +2336,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->tag = value.as_string(); - return true; - } case 3: { this->message = value.as_string(); return true; @@ -2348,7 +2346,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); - buffer.encode_string(2, this->tag); buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } @@ -2360,10 +2357,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->level)); out.append("\n"); - out.append(" tag: "); - out.append("'").append(this->tag).append("'"); - out.append("\n"); - out.append(" message: "); out.append("'").append(this->message).append("'"); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index be32488391..2395a4bec3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -58,9 +58,10 @@ enum LogLevel : uint32_t { LOG_LEVEL_ERROR = 1, LOG_LEVEL_WARN = 2, LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4, - LOG_LEVEL_VERBOSE = 5, - LOG_LEVEL_VERY_VERBOSE = 6, + LOG_LEVEL_CONFIG = 4, + LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_VERBOSE = 6, + LOG_LEVEL_VERY_VERBOSE = 7, }; enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, @@ -627,7 +628,6 @@ class SubscribeLogsRequest : public ProtoMessage { class SubscribeLogsResponse : public ProtoMessage { public: enums::LogLevel level{}; - std::string tag{}; std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; From 9efeea14f2560a93ea3d3f0cc3d0cd268f1e0edc Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 15 Aug 2021 21:40:34 +0200 Subject: [PATCH 106/285] Always send all light state values in API (#2150) --- esphome/components/api/api_connection.cpp | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5fba549a57..f644ec62e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.key = light->get_object_id_hash(); resp.state = values.is_on(); resp.color_mode = static_cast(color_mode); - if (color_mode & light::ColorCapability::BRIGHTNESS) - resp.brightness = values.get_brightness(); - if (color_mode & light::ColorCapability::RGB) { - resp.color_brightness = values.get_color_brightness(); - resp.red = values.get_red(); - resp.green = values.get_green(); - resp.blue = values.get_blue(); - } - if (color_mode & light::ColorCapability::WHITE) - resp.white = values.get_white(); - if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) - resp.color_temperature = values.get_color_temperature(); - if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { - resp.cold_white = values.get_cold_white(); - resp.warm_white = values.get_warm_white(); - } + resp.brightness = values.get_brightness(); + resp.color_brightness = values.get_color_brightness(); + resp.red = values.get_red(); + resp.green = values.get_green(); + resp.blue = values.get_blue(); + resp.white = values.get_white(); + resp.color_temperature = values.get_color_temperature(); + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); From bd457f64d88d717fbf9dba9bca8d7fba131c0a27 Mon Sep 17 00:00:00 2001 From: puuu Date: Mon, 16 Aug 2021 04:59:29 +0900 Subject: [PATCH 107/285] let sensors announce its state_class via mqtt (#2155) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index c106a95902..ce7e89c584 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; + if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) + root["state_class"] = "measurement"; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { From 02b5a3efb8144ad982b722267be6b88a216d74ee Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 15 Aug 2021 16:31:48 -0500 Subject: [PATCH 108/285] Thermostat delayed fan mode fix (#2158) --- .../thermostat/thermostat_climate.cpp | 24 +++++++++++-------- .../thermostat/thermostat_climate.h | 7 ++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d9d8b106ea..a75713cbb9 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -57,29 +57,34 @@ void ThermostatClimate::refresh() { } bool ThermostatClimate::climate_action_change_delayed() { + bool state_mismatch = this->action != this->compute_action_(true); + switch (this->compute_action_(true)) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - return !this->idle_action_ready_(); + return state_mismatch && (!this->idle_action_ready_()); case climate::CLIMATE_ACTION_COOLING: - return !this->cooling_action_ready_(); + return state_mismatch && (!this->cooling_action_ready_()); case climate::CLIMATE_ACTION_HEATING: - return !this->heating_action_ready_(); + return state_mismatch && (!this->heating_action_ready_()); case climate::CLIMATE_ACTION_FAN: - return !this->fanning_action_ready_(); + return state_mismatch && (!this->fanning_action_ready_()); case climate::CLIMATE_ACTION_DRYING: - return !this->drying_action_ready_(); + return state_mismatch && (!this->drying_action_ready_()); default: break; } return false; } -bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } +bool ThermostatClimate::fan_mode_change_delayed() { + bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_; + return state_mismatch && (!this->fan_mode_ready_()); +} climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } -climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } +climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; } bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && @@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - this->desired_fan_mode_ = fan_mode; // needed for timer callback + this->fan_mode = fan_mode; if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->start_timer_(thermostat::TIMER_FAN_MODE); assert(trig != nullptr); trig->trigger(); - this->fan_mode = fan_mode; this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); this->timer_[thermostat::TIMER_FAN_MODE].active = false; - this->switch_to_fan_mode_(this->desired_fan_mode_); + this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 1a5fd82ac0..60777e7c81 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool fan_mode_change_delayed(); /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) climate::ClimateAction delayed_climate_action(); - /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) - climate::ClimateFanMode delayed_fan_mode(); + /// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode locked_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; - /// Desired fan_mode -- used to store desired mode for callback when switching is delayed - climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; - /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called From 1be9bac3a97c824e7ad222caebd39b2c570f2d82 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 16 Aug 2021 01:57:50 +0200 Subject: [PATCH 109/285] Fix native API log level enum values (#2151) --- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 11 ++--------- esphome/components/api/api_pb2.h | 8 ++++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b9b56d3f8e..d21e82f50c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -555,9 +555,10 @@ enum LogLevel { LOG_LEVEL_ERROR = 1; LOG_LEVEL_WARN = 2; LOG_LEVEL_INFO = 3; - LOG_LEVEL_DEBUG = 4; - LOG_LEVEL_VERBOSE = 5; - LOG_LEVEL_VERY_VERBOSE = 6; + LOG_LEVEL_CONFIG = 4; + LOG_LEVEL_DEBUG = 5; + LOG_LEVEL_VERBOSE = 6; + LOG_LEVEL_VERY_VERBOSE = 7; } message SubscribeLogsRequest { option (id) = 28; @@ -572,7 +573,6 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - string tag = 2; string message = 3; bool send_failed = 4; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f644ec62e0..4a31f15e77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -694,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin auto buffer = this->create_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); - // string tag = 2; - // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index eecba7a68e..aaeef57324 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -120,6 +120,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "LOG_LEVEL_WARN"; case enums::LOG_LEVEL_INFO: return "LOG_LEVEL_INFO"; + case enums::LOG_LEVEL_CONFIG: + return "LOG_LEVEL_CONFIG"; case enums::LOG_LEVEL_DEBUG: return "LOG_LEVEL_DEBUG"; case enums::LOG_LEVEL_VERBOSE: @@ -2334,10 +2336,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->tag = value.as_string(); - return true; - } case 3: { this->message = value.as_string(); return true; @@ -2348,7 +2346,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); - buffer.encode_string(2, this->tag); buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } @@ -2360,10 +2357,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->level)); out.append("\n"); - out.append(" tag: "); - out.append("'").append(this->tag).append("'"); - out.append("\n"); - out.append(" message: "); out.append("'").append(this->message).append("'"); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index be32488391..2395a4bec3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -58,9 +58,10 @@ enum LogLevel : uint32_t { LOG_LEVEL_ERROR = 1, LOG_LEVEL_WARN = 2, LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4, - LOG_LEVEL_VERBOSE = 5, - LOG_LEVEL_VERY_VERBOSE = 6, + LOG_LEVEL_CONFIG = 4, + LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_VERBOSE = 6, + LOG_LEVEL_VERY_VERBOSE = 7, }; enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, @@ -627,7 +628,6 @@ class SubscribeLogsRequest : public ProtoMessage { class SubscribeLogsResponse : public ProtoMessage { public: enums::LogLevel level{}; - std::string tag{}; std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; From 14e04eb2313f0c4f6daefd6d8a428f13e41a97b7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:32:48 +1200 Subject: [PATCH 110/285] Bump version to v1.21.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2b9418a81c..89be4b7fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b1" +__version__ = "1.21.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 0c370d589766235e6f816587e743194714aec056 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 17 Aug 2021 04:02:38 +0200 Subject: [PATCH 111/285] Initialize color temperature to value within range if possible (#2168) --- esphome/components/light/light_state.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 278229fbd1..030cf4b7a2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -39,6 +39,13 @@ void LightState::setup() { effect->init_internal(this); } + // When supported color temperature range is known, initialize color temperature setting within bounds. + float min_mireds = this->get_traits().get_min_mireds(); + if (min_mireds > 0) { + this->remote_values.set_color_temperature(min_mireds); + this->current_values.set_color_temperature(min_mireds); + } + auto call = this->make_call(); LightStateRTCState recovered{}; switch (this->restore_mode_) { From 3b52a306cd86dcbb95a892ac680ac843ab84c142 Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 17 Aug 2021 18:05:37 +1000 Subject: [PATCH 112/285] Add a dummy color temp (#2161) --- esphome/components/hbridge/hbridge_light_output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 2f4f87134c..c309154852 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); + traits.set_min_mireds(153); + traits.set_max_mireds(500); return traits; } From 607e1f823d66e907f6a6a53ed40ea3cb004863f0 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 17 Aug 2021 10:12:29 +0200 Subject: [PATCH 113/285] Minor code cleanup in light components (#2162) Co-authored-by: Maurice Makaay --- esphome/components/light/addressable_light.cpp | 2 +- esphome/components/light/base_light_effects.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 12eab6a685..9a34dde6be 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -19,7 +19,7 @@ void AddressableLight::call_setup() { ESP_LOGVV(TAG, " [%2d] Color: R=%3u G=%3u B=%3u W=%3u", i, color.get_red_raw(), color.get_green_raw(), color.get_blue_raw(), color.get_white_raw()); } - ESP_LOGVV(TAG, ""); + ESP_LOGVV(TAG, " "); }); #endif } diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index f66b90f665..7826b2eecb 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -196,7 +196,6 @@ class FlickerLightEffect : public LightEffect { out.set_warm_white(remote.get_warm_white() * beta + current.get_warm_white() * alpha + (random_cubic_float() * this->intensity_)); - auto traits = this->state_->get_traits(); auto call = this->state_->make_call(); call.set_publish(false); call.set_save(false); From ebabf0e7d83dee43d8f335b3a2057cccb2f6d6e8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:43:51 +0200 Subject: [PATCH 114/285] Add Gas device class to DSMR component (#2169) --- esphome/components/dsmr/sensor.py | 5 +++-- esphome/components/sensor/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 05c568e21a..84b263c2d5 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, @@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), @@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 19f2a9f0e6..390cfcf551 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -40,6 +40,7 @@ from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -62,6 +63,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, From 8eb18995cb70f502896f381c6af723d08e376ee6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:44:05 +0200 Subject: [PATCH 115/285] Add device class update to binary sensor (#2170) --- esphome/components/binary_sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 3c2169a922..9cd2a045ff 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -48,6 +48,7 @@ from esphome.const import ( DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -79,6 +80,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 50bac3ca8d..b0f11d1c1a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -813,6 +813,7 @@ DEVICE_CLASS_PROBLEM = "problem" DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" +DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From ebaa84617feb8b07e54c6bac012c285e4c077d01 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 17 Aug 2021 14:16:02 -0500 Subject: [PATCH 116/285] Total daily energy methods (#2163) Co-authored-by: Chris Nussbaum --- .../components/total_daily_energy/sensor.py | 11 ++++++++++ .../total_daily_energy/total_daily_energy.cpp | 21 ++++++++++++++++++- .../total_daily_energy/total_daily_energy.h | 9 ++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index ec38daaf57..f6e92149d6 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + CONF_METHOD, ) DEPENDENCIES = ["time"] @@ -14,6 +15,12 @@ DEPENDENCIES = ["time"] CONF_POWER_ID = "power_id" CONF_MIN_SAVE_INTERVAL = "min_save_interval" total_daily_energy_ns = cg.esphome_ns.namespace("total_daily_energy") +TotalDailyEnergyMethod = total_daily_energy_ns.enum("TotalDailyEnergyMethod") +TOTAL_DAILY_ENERGY_METHODS = { + "trapezoid": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID, + "left": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_LEFT, + "right": TotalDailyEnergyMethod.TOTAL_DAILY_ENERGY_METHOD_RIGHT, +} TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) @@ -33,6 +40,9 @@ CONFIG_SCHEMA = ( cv.Optional( CONF_MIN_SAVE_INTERVAL, default="0s" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_METHOD, default="right"): cv.enum( + TOTAL_DAILY_ENERGY_METHODS, lower=True + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -50,3 +60,4 @@ async def to_code(config): time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time(time_)) cg.add(var.set_min_save_interval(config[CONF_MIN_SAVE_INTERVAL])) + cg.add(var.set_method(config[CONF_METHOD])) diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 1e60442ae7..83333acab7 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -20,7 +20,9 @@ void TotalDailyEnergy::setup() { this->parent_->add_on_state_callback([this](float state) { this->process_new_state_(state); }); } + void TotalDailyEnergy::dump_config() { LOG_SENSOR("", "Total Daily Energy", this); } + void TotalDailyEnergy::loop() { auto t = this->time_->now(); if (!t.is_valid()) @@ -37,6 +39,7 @@ void TotalDailyEnergy::loop() { this->publish_state_and_save(0); } } + void TotalDailyEnergy::publish_state_and_save(float state) { this->total_energy_ = state; this->publish_state(state); @@ -47,13 +50,29 @@ void TotalDailyEnergy::publish_state_and_save(float state) { this->last_save_ = now; this->pref_.save(&state); } + void TotalDailyEnergy::process_new_state_(float state) { if (isnan(state)) return; const uint32_t now = millis(); + const float old_state = this->last_power_state_; + const float new_state = state; float delta_hours = (now - this->last_update_) / 1000.0f / 60.0f / 60.0f; + float delta_energy = 0.0f; + switch (this->method_) { + case TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID: + delta_energy = delta_hours * (old_state + new_state) / 2.0; + break; + case TOTAL_DAILY_ENERGY_METHOD_LEFT: + delta_energy = delta_hours * old_state; + break; + case TOTAL_DAILY_ENERGY_METHOD_RIGHT: + delta_energy = delta_hours * new_state; + break; + } + this->last_power_state_ = new_state; this->last_update_ = now; - this->publish_state_and_save(this->total_energy_ + state * delta_hours); + this->publish_state_and_save(this->total_energy_ + delta_energy); } } // namespace total_daily_energy diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index 123446c534..fd71b8decc 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -8,11 +8,18 @@ namespace esphome { namespace total_daily_energy { +enum TotalDailyEnergyMethod { + TOTAL_DAILY_ENERGY_METHOD_TRAPEZOID = 0, + TOTAL_DAILY_ENERGY_METHOD_LEFT, + TOTAL_DAILY_ENERGY_METHOD_RIGHT, +}; + class TotalDailyEnergy : public sensor::Sensor, public Component { public: void set_min_save_interval(uint32_t min_interval) { this->min_save_interval_ = min_interval; } void set_time(time::RealTimeClock *time) { time_ = time; } void set_parent(Sensor *parent) { parent_ = parent; } + void set_method(TotalDailyEnergyMethod method) { method_ = method; } void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -29,11 +36,13 @@ class TotalDailyEnergy : public sensor::Sensor, public Component { ESPPreferenceObject pref_; time::RealTimeClock *time_; Sensor *parent_; + TotalDailyEnergyMethod method_; uint16_t last_day_of_year_{}; uint32_t last_update_{0}; uint32_t last_save_{0}; uint32_t min_save_interval_{0}; float total_energy_{0.0f}; + float last_power_state_{0.0f}; }; } // namespace total_daily_energy From edb3b77916069d378ec5fb442a5c578663666d97 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:22:00 +1200 Subject: [PATCH 117/285] Send dirty states when screen wakes up (#2167) --- esphome/components/nextion/nextion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 14c7b34a4c..3f44fe4075 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() { ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); this->is_sleeping_ = false; this->wake_callback_.call(); + this->all_components_send_state_(false); break; } case 0x88: // system successful start up From fbd9e87b513c20ea30faad76c290b6c94a73c6c5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:32:19 +1200 Subject: [PATCH 118/285] Remove specified accuracy_decimals from total_daily_energy (#2174) --- esphome/components/total_daily_energy/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index f6e92149d6..f1449432a0 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -27,7 +27,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, From f0b14055b69cc5248655a2c13c06b75c6cb6b1c0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:04:13 +1200 Subject: [PATCH 119/285] Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 4 ++++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 13 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d21e82f50c..e3ef2d7c9e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -448,6 +448,7 @@ message LightCommandRequest { enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; + STATE_CLASS_TOTAL_INCREASING = 2; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index aaeef57324..f5860bee64 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_NONE"; case enums::STATE_CLASS_MEASUREMENT: return "STATE_CLASS_MEASUREMENT"; + case enums::STATE_CLASS_TOTAL_INCREASING: + return "STATE_CLASS_TOTAL_INCREASING"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2395a4bec3..93bfcd9b55 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -47,6 +47,7 @@ enum ColorMode : uint32_t { enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 390cfcf551..1bb4e25a17 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -81,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass") STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, + "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6e8765a8df..1a5c76db51 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; + case STATE_CLASS_TOTAL_INCREASING: + return "total_increasing"; case STATE_CLASS_NONE: default: return ""; @@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state void Sensor::set_state_class(const std::string &state_class) { if (str_equals_case_insensitive(state_class, "measurement")) { this->state_class = STATE_CLASS_MEASUREMENT; + } else if (str_equals_case_insensitive(state_class, "total_increasing")) { + this->state_class = STATE_CLASS_TOTAL_INCREASING; } else { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index b9908b6cbe..f0d7ba4887 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -37,6 +37,7 @@ namespace sensor { enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; const char *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index b0f11d1c1a..d7cd57e05d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -841,6 +841,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a total that only increases, a decrease is considered a reset. +STATE_CLASS_TOTAL_INCREASING = "total_increasing" + # This sensor does not support resetting. ie, it is not accumulative LAST_RESET_TYPE_NONE = "" # This sensor is expected to never reset its value From 9ee3463d07ed0243a6bb827dc7dbd1115ec4bb1f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 17 Aug 2021 04:02:38 +0200 Subject: [PATCH 120/285] Initialize color temperature to value within range if possible (#2168) --- esphome/components/light/light_state.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 278229fbd1..030cf4b7a2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -39,6 +39,13 @@ void LightState::setup() { effect->init_internal(this); } + // When supported color temperature range is known, initialize color temperature setting within bounds. + float min_mireds = this->get_traits().get_min_mireds(); + if (min_mireds > 0) { + this->remote_values.set_color_temperature(min_mireds); + this->current_values.set_color_temperature(min_mireds); + } + auto call = this->make_call(); LightStateRTCState recovered{}; switch (this->restore_mode_) { From cbdb96f1058e33201972b97d9b16e0180c539512 Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 17 Aug 2021 18:05:37 +1000 Subject: [PATCH 121/285] Add a dummy color temp (#2161) --- esphome/components/hbridge/hbridge_light_output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 2f4f87134c..c309154852 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); + traits.set_min_mireds(153); + traits.set_max_mireds(500); return traits; } From 44bb5a89c8ee5756bea37f1a425ba6d87120cf8f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:43:51 +0200 Subject: [PATCH 122/285] Add Gas device class to DSMR component (#2169) --- esphome/components/dsmr/sensor.py | 5 +++-- esphome/components/sensor/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 05c568e21a..84b263c2d5 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, @@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), @@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 19f2a9f0e6..390cfcf551 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -40,6 +40,7 @@ from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -62,6 +63,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, From 9c605f2d46571a327bab46d4e9b4a3e9f62c8eb4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:22:00 +1200 Subject: [PATCH 123/285] Send dirty states when screen wakes up (#2167) --- esphome/components/nextion/nextion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 14c7b34a4c..3f44fe4075 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() { ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); this->is_sleeping_ = false; this->wake_callback_.call(); + this->all_components_send_state_(false); break; } case 0x88: // system successful start up From 61a9c9fa3327499437349311e9a9c35bf355c7cf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:32:19 +1200 Subject: [PATCH 124/285] Remove specified accuracy_decimals from total_daily_energy (#2174) --- esphome/components/total_daily_energy/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index ec38daaf57..6e75feae48 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -20,7 +20,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, From 4e3b95d120afc0c0d247131ae6db27531e480628 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:04:13 +1200 Subject: [PATCH 125/285] Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 4 ++++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 13 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d21e82f50c..e3ef2d7c9e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -448,6 +448,7 @@ message LightCommandRequest { enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; + STATE_CLASS_TOTAL_INCREASING = 2; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index aaeef57324..f5860bee64 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_NONE"; case enums::STATE_CLASS_MEASUREMENT: return "STATE_CLASS_MEASUREMENT"; + case enums::STATE_CLASS_TOTAL_INCREASING: + return "STATE_CLASS_TOTAL_INCREASING"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2395a4bec3..93bfcd9b55 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -47,6 +47,7 @@ enum ColorMode : uint32_t { enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 390cfcf551..1bb4e25a17 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -81,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass") STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, + "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6e8765a8df..1a5c76db51 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; + case STATE_CLASS_TOTAL_INCREASING: + return "total_increasing"; case STATE_CLASS_NONE: default: return ""; @@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state void Sensor::set_state_class(const std::string &state_class) { if (str_equals_case_insensitive(state_class, "measurement")) { this->state_class = STATE_CLASS_MEASUREMENT; + } else if (str_equals_case_insensitive(state_class, "total_increasing")) { + this->state_class = STATE_CLASS_TOTAL_INCREASING; } else { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index b9908b6cbe..f0d7ba4887 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -37,6 +37,7 @@ namespace sensor { enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; const char *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index 89be4b7fd7..010ea1290a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -840,6 +840,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a total that only increases, a decrease is considered a reset. +STATE_CLASS_TOTAL_INCREASING = "total_increasing" + # This sensor does not support resetting. ie, it is not accumulative LAST_RESET_TYPE_NONE = "" # This sensor is expected to never reset its value From 409d4b9d475c6abf142a451fe0a740dfe6c853df Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:11:39 +1200 Subject: [PATCH 126/285] Bump version to v1.21.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 010ea1290a..a17152a64b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b2" +__version__ = "1.21.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From d4c2a85f9c79ba7aebc9d99aed584ad54460f7a7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 14:25:10 +1200 Subject: [PATCH 127/285] Bump version to v2021.8.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17152a64b..e25ba7e046 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b3" +__version__ = "2021.8.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2e59ad90cc0ef374c7360e0bde1c9ce44d271363 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:10:44 +1200 Subject: [PATCH 128/285] Fix docker release for new tags without `v` --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b15b540f6..3dc4952422 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: id: tag run: | if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" else TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") today="$(date --utc '+%Y%m%d')" @@ -138,7 +138,7 @@ jobs: - env: TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} run: | - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" curl \ -u ":$TOKEN" \ -X POST \ From 2100ef63a93ecbc18d56a7012954268ef5538909 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:19:25 +1200 Subject: [PATCH 129/285] Bump version to 2021.9.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5ba5efb66d..d8134bc45f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.8.0" +__version__ = "2021.9.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 21f8fd9fa54ac667ffc5124cea06df22bd3760db Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:39:57 +1200 Subject: [PATCH 130/285] Fix pypi download url (#2177) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44a5965887..967eadd70f 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) -DOWNLOAD_URL = "{}/archive/v{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) here = os.path.abspath(os.path.dirname(__file__)) From 5cb56bc6774a52664703941796fa80f5de000877 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:28:58 +1200 Subject: [PATCH 131/285] Set SDM voltage state class to measurement (#2181) --- esphome/components/sdm_meter/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 13cc94786c..c3af47a664 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -47,6 +47,7 @@ PHASE_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, From b0fa317302df4f6adeab56034e0b6099f58aa4c4 Mon Sep 17 00:00:00 2001 From: puuu Date: Sat, 21 Aug 2021 19:26:24 +0900 Subject: [PATCH 132/285] Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186) --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 0f5b7b4b93..77c377d39e 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 8cc3cbb22e6f0b2d25d7233e54bb7f51f2ea5efb Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 09:19:21 +0200 Subject: [PATCH 133/285] Add macros header with more usable Arduino version defines (#2145) --- .../components/esp8266_pwm/esp8266_pwm.cpp | 5 +- .../neopixelbus/neopixelbus_light.h | 5 +- esphome/components/wifi/wifi_component.h | 3 +- .../wifi/wifi_component_esp8266.cpp | 15 ++--- esphome/core/esphal.cpp | 3 +- esphome/core/macros.h | 56 +++++++++++++++++++ 6 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 esphome/core/macros.h diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 37a9f3efbf..d725e90edc 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,9 +1,10 @@ #include "esp8266_pwm.h" +#include "esphome/core/macros.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 -#error ESP8266 PWM requires at least arduino_core_version 2.4.0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#error ESP8266 PWM requires at least arduino_version 2.4.0 #endif #include diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index c7f7badc5a..1f2cde0bd2 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -1,13 +1,14 @@ #pragma once +#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/color.h" #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 -#error The NeoPixelBus library requires at least arduino_core_version 2.4.x +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#error The NeoPixelBus library requires at least arduino_version 2.4.x #endif #include "NeoPixelBus.h" diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index f698e09d93..d3b086553c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/automation.h" @@ -17,7 +18,7 @@ #include #include -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 2f6c32aec6..c743d253a6 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,4 +1,5 @@ #include "wifi_component.h" +#include "esphome/core/macros.h" #ifdef ARDUINO_ARCH_ESP8266 @@ -10,10 +11,6 @@ #include #endif -#ifdef WIFI_IS_OFF_AT_BOOT // Identifies ESP8266 Arduino 3.0.0 -#define ARDUINO_ESP8266_RELEASE_3 -#endif - extern "C" { #include "lwip/err.h" #include "lwip/dns.h" @@ -22,7 +19,7 @@ extern "C" { #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif -#ifdef ARDUINO_ESP8266_RELEASE_3 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) @@ -229,7 +226,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.bssid_set = 0; } -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { @@ -495,7 +492,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); break; } -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", get_op_mode_str(it.old_opmode), @@ -580,7 +577,7 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = 1; -#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; if (FIRST_SCAN) { config.scan_time.active.min = 100; @@ -659,7 +656,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#ifdef ARDUINO_ESP8266_RELEASE_3 +#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) dhcpSoftAP.begin(&info); #endif diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 6b8350991e..7851ba01b6 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -1,4 +1,5 @@ #include "esphome/core/esphal.h" +#include "esphome/core/macros.h" #include "esphome/core/helpers.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -298,7 +299,7 @@ void force_link_symbols() { } // namespace esphome -#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) // Fix 2.3.0 std missing memchr extern "C" { void *memchr(const void *s, int c, size_t n) { diff --git a/esphome/core/macros.h b/esphome/core/macros.h new file mode 100644 index 0000000000..59b52bf7a1 --- /dev/null +++ b/esphome/core/macros.h @@ -0,0 +1,56 @@ +#pragma once + +#define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) + +#if defined(ARDUINO_ARCH_ESP8266) + +#include +#if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ +#define ARDUINO_VERSION_CODE VERSION_CODE(ARDUINO_ESP8266_MAJOR, ARDUINO_ESP8266_MINOR, ARDUINO_ESP8266_REVISION) +#elif ARDUINO_ESP8266_GIT_VER == 0xefb0341a // version defines were screwed up in v3.0.0 +#define ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_4) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 4) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_3) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 3) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_7_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_3) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 3) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_6_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_5_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_2) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 2) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_1) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 1) +#elif defined(ARDUINO_ESP8266_RELEASE_2_4_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 0) +#elif defined(ARDUINO_ESP8266_RELEASE_2_3_0) +#define ARDUINO_VERSION_CODE VERSION_CODE(2, 3, 0) +#else +#warning "Could not determine Arduino framework version, update esphome/core/macros.h!" +#endif + +#elif defined(ARDUINO_ARCH_ESP32) + +#if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header +#include +#define ARDUINO_VERSION_CODE \ + VERSION_CODE(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATH) +#else +#define ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 0) // there are no defines identifying minor/patch version +#endif + +#endif From 5ec9bb0fb5cc446ea5d9a8de9c22a583eeb52700 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 09:21:30 +0200 Subject: [PATCH 134/285] Clean-up constant definitions (#2148) --- esphome/components/api/api_connection.cpp | 4 +- .../components/bme680_bsec/bme680_bsec.cpp | 4 +- esphome/components/bme680_bsec/bme680_bsec.h | 2 +- .../esp32_ble_server/ble_server.cpp | 4 +- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/components/wifi/__init__.py | 2 +- esphome/components/wifi/wifi_component.cpp | 14 ++--- esphome/components/wifi/wifi_component.h | 16 ++--- .../components/wifi/wifi_component_esp32.cpp | 8 +-- .../wifi/wifi_component_esp8266.cpp | 6 +- esphome/core/defines.h | 61 +++++++++++++------ esphome/core/version.h | 8 ++- 12 files changed, 77 insertions(+), 56 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4a31f15e77..05dc14269b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -745,9 +745,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.mac_address = get_mac_address_pretty(); resp.esphome_version = ESPHOME_VERSION; resp.compilation_time = App.get_compilation_time(); -#ifdef ARDUINO_BOARD - resp.model = ARDUINO_BOARD; -#endif + resp.model = ESPHOME_BOARD; #ifdef USE_DEEP_SLEEP resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; #endif diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 8f53180296..e2cb7491a6 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "bme680_bsec.sensor"; static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; -BME680BSECComponent *BME680BSECComponent::instance; +BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void BME680BSECComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC..."); @@ -359,7 +359,7 @@ void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float va sensor->publish_state(value); } -void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) { +void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) { if (!sensor || (sensor->has_state() && sensor->state == value)) { return; } diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 73994b7541..365aec725e 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -70,7 +70,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { int64_t get_time_ns_(); void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false); - void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value); + void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value); void load_state_(); void save_state_(uint8_t accuracy); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index db83eb6bee..f1eebf3c8a 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -82,11 +82,9 @@ bool BLEServer::create_device_characteristics_() { 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); - model->set_value(ARDUINO_BOARD); -#endif + model->set_value(ESPHOME_BOARD); } BLECharacteristic *version = diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 13acdcacd8..6ddc080b53 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -102,9 +102,7 @@ bool MQTTComponent::send_discovery_() { device_info["identifiers"] = get_mac_address(); device_info["name"] = node_name; device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); -#ifdef ARDUINO_BOARD - device_info["model"] = ARDUINO_BOARD; -#endif + device_info["model"] = ESPHOME_BOARD; device_info["manufacturer"] = "espressif"; }, 0, discovery_info.retain); diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index d066570cc8..c2943d0645 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -305,7 +305,7 @@ def wifi_network(config, static_ip): cg.add(ap.set_password(config[CONF_PASSWORD])) if CONF_EAP in config: cg.add(ap.set_eap(eap_auth(config[CONF_EAP]))) - cg.add_define("ESPHOME_WIFI_WPA2_EAP") + cg.add_define("USE_WIFI_WPA2_EAP") if CONF_BSSID in config: cg.add(ap.set_bssid([HexInt(i) for i in config[CONF_BSSID].parts])) if CONF_HIDDEN in config: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e99cd0e1b1..50feeb6cad 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -258,7 +258,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { ESP_LOGV(TAG, " BSSID: Not Set"); } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { ESP_LOGV(TAG, " WPA2 Enterprise authentication configured:"); EAPAuth eap_config = ap.get_eap().value(); @@ -274,7 +274,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { } else { #endif ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.get_password().c_str()); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP } #endif if (ap.get_channel().has_value()) { @@ -478,7 +478,7 @@ void WiFiComponent::check_scanning_finished() { // copy manual IP (if set) connect_params.set_manual_ip(config.get_manual_ip()); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP // copy EAP parameters (if set) connect_params.set_eap(config.get_eap()); #endif @@ -638,8 +638,8 @@ void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } void WiFiAP::set_password(const std::string &password) { this->password_ = password; } -#ifdef ESPHOME_WIFI_WPA2_EAP -void WiFiAP::set_eap(optional eap_auth) { this->eap_ = eap_auth; } +#ifdef USE_WIFI_WPA2_EAP +void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = std::move(manual_ip); } @@ -647,7 +647,7 @@ void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } const optional &WiFiAP::get_bssid() const { return this->bssid_; } const std::string &WiFiAP::get_password() const { return this->password_; } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif const optional &WiFiAP::get_channel() const { return this->channel_; } @@ -679,7 +679,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) { if (config.get_bssid().has_value() && *config.get_bssid() != this->bssid_) return false; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP // BSSID requires auth but no PSK or EAP credentials given if (this->with_auth_ && (config.get_password().empty() && !config.get_eap().has_value())) return false; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d3b086553c..3a4213c93c 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -63,7 +63,7 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP struct EAPAuth { std::string identity; // required for all auth types std::string username; @@ -73,7 +73,7 @@ struct EAPAuth { const char *client_cert; const char *client_key; }; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP using bssid_t = std::array; @@ -83,9 +83,9 @@ class WiFiAP { void set_bssid(bssid_t bssid); void set_bssid(optional bssid); void set_password(const std::string &password); -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP void set_channel(optional channel); void set_priority(float priority) { priority_ = priority; } void set_manual_ip(optional manual_ip); @@ -93,9 +93,9 @@ class WiFiAP { const std::string &get_ssid() const; const optional &get_bssid() const; const std::string &get_password() const; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP const optional &get_channel() const; float get_priority() const { return priority_; } const optional &get_manual_ip() const; @@ -105,9 +105,9 @@ class WiFiAP { std::string ssid_; optional bssid_; std::string password_; -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP optional eap_; -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP optional channel_; float priority_{0}; optional manual_ip_; diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index 1bccf08a7f..57c4efcdd5 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -6,7 +6,7 @@ #include #include -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP #include #endif #include "lwip/err.h" @@ -163,7 +163,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; } -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE; } @@ -220,7 +220,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } // setup enterprise authentication if required -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. EAPAuth eap = ap.get_eap().value(); @@ -264,7 +264,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); } } -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP this->wifi_apply_hostname_(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c743d253a6..ab68f421a8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -7,7 +7,7 @@ #include #include -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP #include #endif @@ -250,7 +250,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } // setup enterprise authentication if required -#ifdef ESPHOME_WIFI_WPA2_EAP +#ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. EAPAuth eap = ap.get_eap().value(); @@ -293,7 +293,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", ret); } } -#endif // ESPHOME_WIFI_WPA2_EAP +#endif // USE_WIFI_WPA2_EAP this->wifi_apply_hostname_(); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 5c176d1b33..ec10e86586 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -1,31 +1,52 @@ #pragma once -// This file is auto-generated! Do not edit! +// This file is not used by the runtime, instead, a version is generated during +// compilation with only the relevant feature flags for the current build. +// +// This file is only used by static analyzers and IDEs. + +// Informative flags +#define ESPHOME_BOARD "dummy_board" +#define ESPHOME_PROJECT_NAME "dummy project" +#define ESPHOME_PROJECT_VERSION "v2" + +// Feature flags +#define USE_ADC_SENSOR_VCC #define USE_API -#define USE_LOGGER #define USE_BINARY_SENSOR -#define USE_SENSOR -#define USE_SWITCH -#define USE_WIFI -#define USE_STATUS_LED -#define USE_TEXT_SENSOR -#define USE_FAN -#define USE_COVER -#define USE_LIGHT +#define USE_CAPTIVE_PORTAL #define USE_CLIMATE -#define USE_NUMBER -#define USE_SELECT -#define USE_MQTT -#define USE_POWER_SUPPLY +#define USE_COVER +#define USE_DEEP_SLEEP +#define USE_ESP8266_PREFERENCES_FLASH +#define USE_FAN #define USE_HOMEASSISTANT_TIME +#define USE_I2C_MULTIPLEXER #define USE_JSON +#define USE_LIGHT +#define USE_LOGGER +#define USE_MDNS +#define USE_MQTT +#define USE_NUMBER +#define USE_OTA_STATE_CALLBACK +#define USE_POWER_SUPPLY +#define USE_PROMETHEUS +#define USE_SELECT +#define USE_SENSOR +#define USE_STATUS_LED +#define USE_SWITCH +#define USE_TEXT_SENSOR +#define USE_TFT_UPLOAD +#define USE_TIME +#define USE_WIFI +#define USE_WIFI_WPA2_EAP + #ifdef ARDUINO_ARCH_ESP32 -#define USE_ESP32_CAMERA #define USE_ESP32_BLE_SERVER +#define USE_ESP32_CAMERA +#define USE_ETHERNET #define USE_IMPROV #endif -#define USE_TIME -#define USE_DEEP_SLEEP -#define USE_CAPTIVE_PORTAL -#define ESPHOME_BOARD "dummy_board" -#define USE_MDNS + +// Disabled feature flags +//#define USE_BSEC // Requires a library with proprietary license. diff --git a/esphome/core/version.h b/esphome/core/version.h index 0942c3e52f..b64f581b25 100644 --- a/esphome/core/version.h +++ b/esphome/core/version.h @@ -1,3 +1,9 @@ #pragma once -// This file is auto-generated! Do not edit! + +// This file is not used by the runtime, instead, a version is generated during +// compilation with only the version for the current build. This is kept in its +// own file so that not all files have to be recompiled for each new release. +// +// This file is only used by static analyzers and IDEs. + #define ESPHOME_VERSION "dev" From 2f33cd2db544b098141ea1476b58d5569e8824ac Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:00:38 +0200 Subject: [PATCH 135/285] Remove double scheduling from addressable lights (#1963) --- .../adalight/adalight_light_effect.cpp | 2 + .../e131/e131_addressable_light_effect.cpp | 1 + .../components/fastled_base/fastled_light.cpp | 9 ++--- .../components/fastled_base/fastled_light.h | 2 +- .../components/light/addressable_light.cpp | 5 +-- esphome/components/light/addressable_light.h | 8 ++-- .../light/addressable_light_effect.h | 37 ++++++++++++------- esphome/components/light/light_output.h | 7 ++++ esphome/components/light/light_state.cpp | 14 +++---- .../neopixelbus/neopixelbus_light.h | 5 +-- .../components/partition/light_partition.h | 10 ++--- esphome/components/wled/wled_light_effect.cpp | 2 + 12 files changed, 57 insertions(+), 45 deletions(-) diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index 5f60fbe0b2..d9c2892d21 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -44,6 +44,7 @@ void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { it[led].set(Color::BLACK); } + it.schedule_show(); } void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { @@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL it[led].set(Color(led_data[0], led_data[1], led_data[2], white)); } + it.schedule_show(); return CONSUMED; } diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index f280b5bc94..f0f165b25f 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -84,6 +84,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet break; } + it->schedule_show(); return true; } diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index 4d791f5709..edfeb401f1 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -20,13 +20,12 @@ void FastLEDLightOutput::dump_config() { ESP_LOGCONFIG(TAG, " Num LEDs: %u", this->num_leds_); ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_); } -void FastLEDLightOutput::loop() { - if (!this->should_show_()) - return; - - uint32_t now = micros(); +void FastLEDLightOutput::write_state(light::LightState *state) { // protect from refreshing too often + uint32_t now = micros(); if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) { + // try again next loop iteration, so that this change won't get lost + this->schedule_show(); return; } this->last_refresh_ = now; diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index ac6acc95a5..ee85735dea 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -213,7 +213,7 @@ class FastLEDLightOutput : public light::AddressableLight { } void setup() override; void dump_config() override; - void loop() override; + void write_state(light::LightState *state) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void clear_effect_data() override { diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 9a34dde6be..1b1bc88a6f 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -12,8 +12,7 @@ void AddressableLight::call_setup() { #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE this->set_interval(5000, [this]() { const char *name = this->state_parent_ == nullptr ? "" : this->state_parent_->get_name().c_str(); - ESP_LOGVV(TAG, "Addressable Light '%s' (effect_active=%s next_show=%s)", name, YESNO(this->effect_active_), - YESNO(this->next_show_)); + ESP_LOGVV(TAG, "Addressable Light '%s' (effect_active=%s)", name, YESNO(this->effect_active_)); for (int i = 0; i < this->size(); i++) { auto color = this->get(i); ESP_LOGVV(TAG, " [%2d] Color: R=%3u G=%3u B=%3u W=%3u", i, color.get_red_raw(), color.get_green_raw(), @@ -36,7 +35,7 @@ Color esp_color_from_light_color_values(LightColorValues val) { return Color(r, g, b, w); } -void AddressableLight::write_state(LightState *state) { +void AddressableLight::update_state(LightState *state) { auto val = state->current_values; auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state()); this->correction_.set_local_brightness(max_brightness); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index ab1efdf160..bba2158457 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -51,9 +51,9 @@ class AddressableLight : public LightOutput, public Component { amnt = this->size(); this->range(amnt, this->size()) = this->range(0, -amnt); } + // Indicates whether an effect that directly updates the output buffer is active to prevent overwriting bool is_effect_active() const { return this->effect_active_; } void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; } - void write_state(LightState *state) override; std::unique_ptr create_default_transition() override; void set_correction(float red, float green, float blue, float white = 1.0f) { this->correction_.set_max_brightness( @@ -63,7 +63,8 @@ class AddressableLight : public LightOutput, public Component { this->correction_.calculate_gamma_table(state->get_gamma_correct()); this->state_parent_ = state; } - void schedule_show() { this->next_show_ = true; } + void update_state(LightState *state) override; + void schedule_show() { this->state_parent_->next_write_ = true; } #ifdef USE_POWER_SUPPLY void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } @@ -74,9 +75,7 @@ class AddressableLight : public LightOutput, public Component { protected: friend class AddressableLightTransformer; - bool should_show_() const { return this->effect_active_ || this->next_show_; } void mark_shown_() { - this->next_show_ = false; #ifdef USE_POWER_SUPPLY for (auto c : *this) { if (c.get().is_on()) { @@ -90,7 +89,6 @@ class AddressableLight : public LightOutput, public Component { virtual ESPColorView get_view_internal(int32_t index) const = 0; bool effect_active_{false}; - bool next_show_{true}; ESPColorCorrection correction_{}; #ifdef USE_POWER_SUPPLY power_supply::PowerSupplyRequester power_; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 3a2ba66845..1cb29dfa4e 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -63,6 +63,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect { this->last_run_ = now; this->f_(it, current_color, this->initial_run_); this->initial_run_ = false; + it.schedule_show(); } } @@ -87,6 +88,7 @@ class AddressableRainbowLightEffect : public AddressableLightEffect { var = hsv; hue += add; } + it.schedule_show(); } void set_speed(uint32_t speed) { this->speed_ = speed; } void set_width(uint16_t width) { this->width_ = width; } @@ -134,6 +136,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect { new_color.b = c.b; } } + it.schedule_show(); } protected: @@ -151,25 +154,27 @@ class AddressableScanEffect : public AddressableLightEffect { void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } void apply(AddressableLight &it, const Color ¤t_color) override { - it.all() = Color::BLACK; + const uint32_t now = millis(); + if (now - this->last_move_ < this->move_interval_) + return; + if (direction_) { + this->at_led_++; + if (this->at_led_ == it.size() - this->scan_width_) + this->direction_ = false; + } else { + this->at_led_--; + if (this->at_led_ == 0) + this->direction_ = true; + } + this->last_move_ = now; + + it.all() = Color::BLACK; for (auto i = 0; i < this->scan_width_; i++) { it[this->at_led_ + i] = current_color; } - const uint32_t now = millis(); - if (now - this->last_move_ > this->move_interval_) { - if (direction_) { - this->at_led_++; - if (this->at_led_ == it.size() - this->scan_width_) - this->direction_ = false; - } else { - this->at_led_--; - if (this->at_led_ == 0) - this->direction_ = true; - } - this->last_move_ = now; - } + it.schedule_show(); } protected: @@ -210,6 +215,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect { continue; addressable[pos].set_effect_data(1); } + addressable.schedule_show(); } void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; } void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; } @@ -257,6 +263,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect { const uint8_t color = random_uint32() & 0b111; it[pos].set_effect_data(0b1000 | color); } + it.schedule_show(); } void set_twinkle_probability(float twinkle_probability) { this->twinkle_probability_ = twinkle_probability; } void set_progress_interval(uint32_t progress_interval) { this->progress_interval_ = progress_interval; } @@ -301,6 +308,7 @@ class AddressableFireworksEffect : public AddressableLightEffect { it[pos] = current_color; } } + it.schedule_show(); } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void set_spark_probability(float spark_probability) { this->spark_probability_ = spark_probability; } @@ -335,6 +343,7 @@ class AddressableFlickerEffect : public AddressableLightEffect { // slowly fade back to "real" value var = (var.get() * inv_intensity) + (current_color * intensity); } + it.schedule_show(); } void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void set_intensity(float intensity) { this->intensity_ = to_uint8_scale(intensity); } diff --git a/esphome/components/light/light_output.h b/esphome/components/light/light_output.h index 7568ea6831..73ba0371cd 100644 --- a/esphome/components/light/light_output.h +++ b/esphome/components/light/light_output.h @@ -19,6 +19,13 @@ class LightOutput { virtual void setup_state(LightState *state) {} + /// Called on every update of the current values of the associated LightState, + /// can optionally be used to do processing of this change. + virtual void update_state(LightState *state) {} + + /// Called from loop() every time the light state has changed, and should + /// should write the new state to hardware. Every call to write_state() is + /// preceded by (at least) one call to update_state(). virtual void write_state(LightState *state) = 0; }; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 030cf4b7a2..4c4eefdc30 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -114,9 +114,11 @@ void LightState::loop() { // Apply transformer (if any) if (this->transformer_ != nullptr) { auto values = this->transformer_->apply(); - this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to - if (values.has_value()) + if (values.has_value()) { this->current_values = *values; + this->output_->update_state(this); + this->next_write_ = true; + } if (this->transformer_->is_finished()) { this->transformer_->stop(); @@ -127,18 +129,15 @@ void LightState::loop() { // Write state to the light if (this->next_write_) { - this->output_->write_state(this); this->next_write_ = false; + this->output_->write_state(this); } } float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } uint32_t LightState::hash_base() { return 1114400283; } -void LightState::publish_state() { - this->remote_values_callback_.call(); - this->next_write_ = true; -} +void LightState::publish_state() { this->remote_values_callback_.call(); } LightOutput *LightState::get_output() const { return this->output_; } std::string LightState::get_effect_name() { @@ -248,6 +247,7 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot if (set_remote_values) { this->remote_values = target; } + this->output_->update_state(this); this->next_write_ = true; } diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 1f2cde0bd2..6fa3fb3cd9 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -83,10 +83,7 @@ class NeoPixelBusLightOutputBase : public light::AddressableLight { this->controller_->Begin(); } - void loop() override { - if (!this->should_show_()) - return; - + void write_state(light::LightState *state) override { this->mark_shown_(); this->controller_->Dirty(); diff --git a/esphome/components/partition/light_partition.h b/esphome/components/partition/light_partition.h index 687fe562d1..f74001cf75 100644 --- a/esphome/components/partition/light_partition.h +++ b/esphome/components/partition/light_partition.h @@ -50,13 +50,11 @@ class PartitionLightOutput : public light::AddressableLight { } } light::LightTraits get_traits() override { return this->segments_[0].get_src()->get_traits(); } - void loop() override { - if (this->should_show_()) { - for (auto seg : this->segments_) { - seg.get_src()->schedule_show(); - } - this->mark_shown_(); + void write_state(light::LightState *state) override { + for (auto seg : this->segments_) { + seg.get_src()->schedule_show(); } + this->mark_shown_(); } protected: diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 690d2f3b00..915d1c6cc2 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -42,6 +42,7 @@ void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { for (int led = it.size(); led-- > 0;) { it[led].set(Color::BLACK); } + it.schedule_show(); } void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { @@ -134,6 +135,7 @@ bool WLEDLightEffect::parse_frame_(light::AddressableLight &it, const uint8_t *p blank_at_ = millis() + DEFAULT_BLANK_TIME; } + it.schedule_show(); return true; } From d71996e58d57d70446d9eb4d6cc023263e9c779c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:43:54 +0200 Subject: [PATCH 136/285] Reduce static RAM usage (#2140) --- esphome/components/light/light_call.cpp | 37 +++--- esphome/components/ota/ota_component.cpp | 11 +- esphome/components/sntp/sntp_component.cpp | 5 +- esphome/components/time/real_time_clock.cpp | 5 +- .../wifi/wifi_component_esp8266.cpp | 112 ++++++++++-------- esphome/core/log.h | 25 ++++ 6 files changed, 113 insertions(+), 82 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 6945d37ded..d979b13368 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -8,26 +8,23 @@ namespace light { static const char *const TAG = "light"; static const char *color_mode_to_human(ColorMode color_mode) { - switch (color_mode) { - case ColorMode::UNKNOWN: - return "Unknown"; - case ColorMode::WHITE: - return "White"; - case ColorMode::COLOR_TEMPERATURE: - return "Color temperature"; - case ColorMode::COLD_WARM_WHITE: - return "Cold/warm white"; - case ColorMode::RGB: - return "RGB"; - case ColorMode::RGB_WHITE: - return "RGBW"; - case ColorMode::RGB_COLD_WARM_WHITE: - return "RGB + cold/warm white"; - case ColorMode::RGB_COLOR_TEMPERATURE: - return "RGB + color temperature"; - default: - return ""; - } + if (color_mode == ColorMode::UNKNOWN) + return "Unknown"; + if (color_mode == ColorMode::WHITE) + return "White"; + if (color_mode == ColorMode::COLOR_TEMPERATURE) + return "Color temperature"; + if (color_mode == ColorMode::COLD_WARM_WHITE) + return "Cold/warm white"; + if (color_mode == ColorMode::RGB) + return "RGB"; + if (color_mode == ColorMode::RGB_WHITE) + return "RGBW"; + if (color_mode == ColorMode::RGB_COLD_WARM_WHITE) + return "RGB + cold/warm white"; + if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) + return "RGB + color temperature"; + return ""; } void LightCall::perform() { diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 71f8101704..ac72befb9e 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -178,28 +178,29 @@ void OTAComponent::handle_() { #endif if (!Update.begin(ota_size, U_FLASH)) { + uint8_t error = Update.getError(); StreamString ss; Update.printError(ss); #ifdef ARDUINO_ARCH_ESP8266 - if (ss.indexOf("Invalid bootstrapping") != -1) { + if (error == UPDATE_ERROR_BOOTSTRAP) { error_code = OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; goto error; } - if (ss.indexOf("new Flash config wrong") != -1 || ss.indexOf("new Flash config wsong") != -1) { + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) { error_code = OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; goto error; } - if (ss.indexOf("Flash config wrong real") != -1 || ss.indexOf("Flash config wsong real") != -1) { + if (error == UPDATE_ERROR_FLASH_CONFIG) { error_code = OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; goto error; } - if (ss.indexOf("Not Enough Space") != -1) { + if (error == UPDATE_ERROR_SPACE) { error_code = OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; goto error; } #endif #ifdef ARDUINO_ARCH_ESP32 - if (ss.indexOf("Bad Size Given") != -1) { + if (error == UPDATE_ERROR_SIZE) { error_code = OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; goto error; } diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index ff176b1d4e..895c775b19 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -56,9 +56,8 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - char buf[128]; - time.strftime(buf, sizeof(buf), "%c"); - ESP_LOGD(TAG, "Synchronized time: %s", buf); + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; } diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index c2a93b5191..27a2d84da6 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -35,9 +35,8 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { } auto time = this->now(); - char buf[128]; - time.strftime(buf, sizeof(buf), "%c"); - ESP_LOGD(TAG, "Synchronized time: %s", buf); + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + time.minute, time.second); this->time_sync_callback_.call(); } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index ab68f421a8..ad1a64d1f4 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -366,65 +366,75 @@ const char *get_op_mode_str(uint8_t mode) { return "UNKNOWN"; } } +// Note that this method returns PROGMEM strings, so use LOG_STR_ARG() to access them. const char *get_disconnect_reason_str(uint8_t reason) { + /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the + * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM + * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM. + */ + if (reason <= REASON_CIPHER_SUITE_REJECTED) { // This must be the last constant with a value <200 + switch (reason) { + case REASON_AUTH_EXPIRE: + return LOG_STR("Auth Expired"); + case REASON_AUTH_LEAVE: + return LOG_STR("Auth Leave"); + case REASON_ASSOC_EXPIRE: + return LOG_STR("Association Expired"); + case REASON_ASSOC_TOOMANY: + return LOG_STR("Too Many Associations"); + case REASON_NOT_AUTHED: + return LOG_STR("Not Authenticated"); + case REASON_NOT_ASSOCED: + return LOG_STR("Not Associated"); + case REASON_ASSOC_LEAVE: + return LOG_STR("Association Leave"); + case REASON_ASSOC_NOT_AUTHED: + return LOG_STR("Association not Authenticated"); + case REASON_DISASSOC_PWRCAP_BAD: + return LOG_STR("Disassociate Power Cap Bad"); + case REASON_DISASSOC_SUPCHAN_BAD: + return LOG_STR("Disassociate Supported Channel Bad"); + case REASON_IE_INVALID: + return LOG_STR("IE Invalid"); + case REASON_MIC_FAILURE: + return LOG_STR("Mic Failure"); + case REASON_4WAY_HANDSHAKE_TIMEOUT: + return LOG_STR("4-Way Handshake Timeout"); + case REASON_GROUP_KEY_UPDATE_TIMEOUT: + return LOG_STR("Group Key Update Timeout"); + case REASON_IE_IN_4WAY_DIFFERS: + return LOG_STR("IE In 4-Way Handshake Differs"); + case REASON_GROUP_CIPHER_INVALID: + return LOG_STR("Group Cipher Invalid"); + case REASON_PAIRWISE_CIPHER_INVALID: + return LOG_STR("Pairwise Cipher Invalid"); + case REASON_AKMP_INVALID: + return LOG_STR("AKMP Invalid"); + case REASON_UNSUPP_RSN_IE_VERSION: + return LOG_STR("Unsupported RSN IE version"); + case REASON_INVALID_RSN_IE_CAP: + return LOG_STR("Invalid RSN IE Cap"); + case REASON_802_1X_AUTH_FAILED: + return LOG_STR("802.1x Authentication Failed"); + case REASON_CIPHER_SUITE_REJECTED: + return LOG_STR("Cipher Suite Rejected"); + } + } + switch (reason) { - case REASON_AUTH_EXPIRE: - return "Auth Expired"; - case REASON_AUTH_LEAVE: - return "Auth Leave"; - case REASON_ASSOC_EXPIRE: - return "Association Expired"; - case REASON_ASSOC_TOOMANY: - return "Too Many Associations"; - case REASON_NOT_AUTHED: - return "Not Authenticated"; - case REASON_NOT_ASSOCED: - return "Not Associated"; - case REASON_ASSOC_LEAVE: - return "Association Leave"; - case REASON_ASSOC_NOT_AUTHED: - return "Association not Authenticated"; - case REASON_DISASSOC_PWRCAP_BAD: - return "Disassociate Power Cap Bad"; - case REASON_DISASSOC_SUPCHAN_BAD: - return "Disassociate Supported Channel Bad"; - case REASON_IE_INVALID: - return "IE Invalid"; - case REASON_MIC_FAILURE: - return "Mic Failure"; - case REASON_4WAY_HANDSHAKE_TIMEOUT: - return "4-Way Handshake Timeout"; - case REASON_GROUP_KEY_UPDATE_TIMEOUT: - return "Group Key Update Timeout"; - case REASON_IE_IN_4WAY_DIFFERS: - return "IE In 4-Way Handshake Differs"; - case REASON_GROUP_CIPHER_INVALID: - return "Group Cipher Invalid"; - case REASON_PAIRWISE_CIPHER_INVALID: - return "Pairwise Cipher Invalid"; - case REASON_AKMP_INVALID: - return "AKMP Invalid"; - case REASON_UNSUPP_RSN_IE_VERSION: - return "Unsupported RSN IE version"; - case REASON_INVALID_RSN_IE_CAP: - return "Invalid RSN IE Cap"; - case REASON_802_1X_AUTH_FAILED: - return "802.1x Authentication Failed"; - case REASON_CIPHER_SUITE_REJECTED: - return "Cipher Suite Rejected"; case REASON_BEACON_TIMEOUT: - return "Beacon Timeout"; + return LOG_STR("Beacon Timeout"); case REASON_NO_AP_FOUND: - return "AP Not Found"; + return LOG_STR("AP Not Found"); case REASON_AUTH_FAIL: - return "Authentication Failed"; + return LOG_STR("Authentication Failed"); case REASON_ASSOC_FAIL: - return "Association Failed"; + return LOG_STR("Association Failed"); case REASON_HANDSHAKE_TIMEOUT: - return "Handshake Failed"; + return LOG_STR("Handshake Failed"); case REASON_UNSPECIFIED: default: - return "Unspecified"; + return LOG_STR("Unspecified"); } } @@ -448,7 +458,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); } break; } diff --git a/esphome/core/log.h b/esphome/core/log.h index 0eec28101f..fbaaf14408 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -7,6 +7,7 @@ #include "WString.h" #endif +#include "esphome/core/macros.h" // avoid esp-idf redefining our macros #include "esphome/core/esphal.h" @@ -162,4 +163,28 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #define ONOFF(b) ((b) ? "ON" : "OFF") #define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") +#ifdef USE_STORE_LOG_STR_IN_FLASH +#define LOG_STR(s) PSTR(s) + +// From Arduino 2.5 onwards, we can pass a PSTR() to printf(). For previous versions, emulate support +// by copying the message to a local buffer first. String length is limited to 63 characters. +// https://github.com/esp8266/Arduino/commit/6280e98b0360f85fdac2b8f10707fffb4f6e6e31 +#include +#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 5, 0) +#define LOG_STR_ARG(s) \ + ({ \ + char __buf[64]; \ + __buf[63] = '\0'; \ + strncpy_P(__buf, s, 63); \ + __buf; \ + }) +#else +#define LOG_STR_ARG(s) (s) +#endif + +#else +#define LOG_STR(s) (s) +#define LOG_STR_ARG(s) (s) +#endif + } // namespace esphome From 518c271eba008568593642d7d06747f6e095e866 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 10:44:24 +0200 Subject: [PATCH 137/285] Fix addressable light control without transitions & effects with transitions (#2187) --- esphome/components/light/addressable_light.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 1b1bc88a6f..a8fa2cd7ac 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -45,9 +45,14 @@ void AddressableLight::update_state(LightState *state) { // don't use LightState helper, gamma correction+brightness is handled by ESPColorView this->all() = esp_color_from_light_color_values(val); + this->schedule_show(); } void AddressableLightTransformer::start() { + // don't try to transition over running effects. + if (this->light_.is_effect_active()) + return; + auto end_values = this->target_values_; this->target_color_ = esp_color_from_light_color_values(end_values); From 71237e2f76f4f7f9e2eef84bb24a01c2326becbd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 23 Aug 2021 21:21:30 +1200 Subject: [PATCH 138/285] Fix template select log message mentioning number (#2194) --- esphome/components/template/select/template_select.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 782c0ee6f9..8695880856 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -11,7 +11,7 @@ void TemplateSelect::setup() { return; std::string value; - ESP_LOGD(TAG, "Setting up Template Number"); + ESP_LOGD(TAG, "Setting up Template Select"); if (!this->restore_value_) { value = this->initial_option_; ESP_LOGD(TAG, "State from initial: %s", value.c_str()); From 1c1ad3261031062dacc532f6a5fd6a54b2c1d0b2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 23 Aug 2021 20:48:12 +0200 Subject: [PATCH 139/285] Add deprecated attribute to some deprecated types/methods (#2185) --- esphome/components/api/api_connection.cpp | 1 + esphome/components/cover/cover.h | 3 +++ esphome/components/fan/fan_helpers.cpp | 4 +++- esphome/components/fan/fan_state.h | 3 ++- esphome/components/mqtt/mqtt_fan.cpp | 10 ++++++---- esphome/components/web_server/web_server.cpp | 8 ++++---- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 05dc14269b..2bf3af5f65 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -289,6 +289,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { // Prefer level call.set_speed(msg.speed_level); } else if (msg.has_speed) { + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8f30750fbd..77a53f11c5 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -125,16 +125,19 @@ class Cover : public Nameable { * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9") void open(); /** Close the cover. * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9") void close(); /** Stop the cover. * * This is a legacy method and may be removed later, please use `.make_call()` instead. */ + ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9") void stop(); void add_on_state_callback(std::function &&f); diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index 09be20991b..5d923a1b15 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -4,12 +4,14 @@ namespace esphome { namespace fan { +// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); + return static_cast(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations) } +// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { const auto enum_level = static_cast(speed) + 1; const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index a0dda4083a..af00275df0 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -10,7 +10,7 @@ namespace esphome { namespace fan { /// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum FanSpeed { +enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { FAN_SPEED_LOW = 0, ///< The fan is running on low speed. FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. @@ -45,6 +45,7 @@ class FanStateCall { this->speed_ = speed; return *this; } + ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") FanStateCall &set_speed(const char *legacy_speed); FanStateCall &set_direction(FanDirection direction) { this->direction_ = direction; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 4171dae04c..ba9121bc5d 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -65,7 +65,9 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { - this->state_->make_call().set_speed(payload.c_str()).perform(); + this->state_->make_call() + .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) + .perform(); }); } @@ -99,16 +101,16 @@ bool MQTTFanComponent::publish_state() { if (traits.supports_speed()) { const char *payload; switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { - case FAN_SPEED_LOW: { + case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "low"; break; } - case FAN_SPEED_MEDIUM: { + case FAN_SPEED_MEDIUM: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "medium"; break; } default: - case FAN_SPEED_HIGH: { + case FAN_SPEED_HIGH: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "high"; break; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 9dad61bb5b..56c75a1c58 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -398,13 +398,13 @@ std::string WebServer::fan_json(fan::FanState *obj) { if (traits.supports_speed()) { root["speed_level"] = obj->speed; switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { - case fan::FAN_SPEED_LOW: + case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "low"; break; - case fan::FAN_SPEED_MEDIUM: + case fan::FAN_SPEED_MEDIUM: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "medium"; break; - case fan::FAN_SPEED_HIGH: + case fan::FAN_SPEED_HIGH: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "high"; break; } @@ -430,7 +430,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); - call.set_speed(speed.c_str()); + call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); From 1b89174558c87841a568e89abbea351fe35fcf17 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 23 Aug 2021 20:49:19 +0200 Subject: [PATCH 140/285] Store source package in Component for debugging (#2070) --- esphome/core/application.cpp | 14 +++++------- esphome/core/component.cpp | 21 +++++++++++++++++- esphome/core/component.h | 22 +++++++++++++++++++ esphome/core/scheduler.cpp | 5 ++++- esphome/cpp_helpers.py | 33 ++++++++++++++++++++++++++++ tests/unit_tests/test_core.py | 6 ++++- tests/unit_tests/test_cpp_helpers.py | 4 ++-- 7 files changed, 91 insertions(+), 14 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1a3158e4ce..fac17a8271 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -19,7 +19,7 @@ void Application::register_component_(Component *comp) { for (auto *c : this->components_) { if (comp == c) { - ESP_LOGW(TAG, "Component already registered! (%p)", c); + ESP_LOGW(TAG, "Component %s already registered! (%p)", c->get_component_source(), c); return; } } @@ -66,23 +66,19 @@ void Application::setup() { } void Application::loop() { uint32_t new_app_state = 0; - const uint32_t start = millis(); this->scheduler.call(); for (Component *component : this->looping_components_) { - component->call(); + { + WarnIfComponentBlockingGuard guard{component}; + component->call(); + } new_app_state |= component->get_component_state(); this->app_state_ |= new_app_state; this->feed_wdt(); } this->app_state_ = new_app_state; - const uint32_t end = millis(); - if (end - start > 200) { - ESP_LOGV(TAG, "A component took a long time in a loop() cycle (%.2f s).", (end - start) / 1e3f); - ESP_LOGV(TAG, "Components should block for at most 20-30ms in loop()."); - } - const uint32_t now = millis(); if (HighFrequencyLoopRequester::is_high_frequency()) { diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index f6b15b1977..e4535e77d9 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -92,8 +92,13 @@ void Component::call() { break; } } +const char *Component::get_component_source() const { + if (this->component_source_ == nullptr) + return ""; + return this->component_source_; +} void Component::mark_failed() { - ESP_LOGE(TAG, "Component was marked as failed."); + ESP_LOGE(TAG, "Component %s was marked as failed.", this->get_component_source()); this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ |= COMPONENT_STATE_FAILED; this->status_set_error(); @@ -190,4 +195,18 @@ uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } +WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) { + component_ = component; + started_ = millis(); +} +WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { + uint32_t now = millis(); + if (now - started_ > 50) { + const char *src = component_ == nullptr ? "" : component_->get_component_source(); + ESP_LOGV(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); + ESP_LOGV(TAG, "Components should block for at most 20-30ms."); + ; + } +} + } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index a4a945ef2a..b9a22c240e 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -130,6 +130,17 @@ class Component { bool has_overridden_loop() const; + /** Set where this component was loaded from for some debug messages. + * + * This is set by the ESPHome core, and should not be called manually. + */ + void set_component_source(const char *source) { component_source_ = source; } + /** Get the integration where this component was declared as a string. + * + * Returns "" if source not set + */ + const char *get_component_source() const; + protected: virtual void call_loop(); virtual void call_setup(); @@ -201,6 +212,7 @@ class Component { uint32_t component_state_{0x0000}; ///< State of this component. float setup_priority_override_{NAN}; + const char *component_source_ = nullptr; }; /** This class simplifies creating components that periodically check a state. @@ -276,4 +288,14 @@ class Nameable { bool disabled_by_default_{false}; }; +class WarnIfComponentBlockingGuard { + public: + WarnIfComponentBlockingGuard(Component *component); + ~WarnIfComponentBlockingGuard(); + + protected: + uint32_t started_; + Component *component_; +}; + } // namespace esphome diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 410c68052f..60e0d4e9bd 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -155,7 +155,10 @@ void ICACHE_RAM_ATTR HOT Scheduler::call() { // Warning: During f(), a lot of stuff can happen, including: // - timeouts/intervals get added, potentially invalidating vector pointers // - timeouts/intervals get cancelled - item->f(); + { + WarnIfComponentBlockingGuard guard{item->component}; + item->f(); + } } { diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1d66eabf6c..7912e4ae06 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,3 +1,5 @@ +import logging + from esphome.const import ( CONF_INVERTED, CONF_MODE, @@ -15,6 +17,9 @@ from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry +_LOGGER = logging.getLogger(__name__) + + async def gpio_pin_expression(conf): """Generate an expression for the given pin option. @@ -42,6 +47,8 @@ async def register_component(var, config): :param var: The variable representing the component. :param config: The configuration for the component. """ + import inspect + id_ = str(var.base) if id_ not in CORE.component_ids: raise ValueError( @@ -54,6 +61,32 @@ async def register_component(var, config): add(var.set_setup_priority(config[CONF_SETUP_PRIORITY])) if CONF_UPDATE_INTERVAL in config: add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) + + # Set component source by inspecting the stack and getting the callee module + # https://stackoverflow.com/a/1095621 + name = None + try: + for frm in inspect.stack()[1:]: + mod = inspect.getmodule(frm[0]) + if mod is None: + continue + name = mod.__name__ + if name.startswith("esphome.components."): + name = name[len("esphome.components.") :] + break + if name == "esphome.automation": + name = "automation" + # continue looking further up in stack in case we find a better one + if name == "esphome.coroutine": + # Only works for async-await coroutine syntax + break + except (KeyError, AttributeError, IndexError) as e: + _LOGGER.warning( + "Error while finding name of component, please report this", exc_info=e + ) + if name is not None: + add(var.set_component_source(name)) + add(App.register_component(var)) return var diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 9e4ad3d79d..37b4d6db57 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -473,7 +473,11 @@ class TestLibrary: ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), ("__eq__", core.Library(name="libbar", version="1.2.3"), False), - ("__eq__", core.Library(name="libbar", version=None, repository="file:///test"), False), + ( + "__eq__", + core.Library(name="libbar", version=None, repository="file:///test"), + False, + ), ("__eq__", 1000, NotImplemented), ("__eq__", "1000", NotImplemented), ("__eq__", True, NotImplemented), diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index 3e317589a9..ae7a61e01f 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -38,7 +38,7 @@ async def test_register_component(monkeypatch): actual = await ch.register_component(var, {}) assert actual is var - add_mock.assert_called_once() + assert add_mock.call_count == 2 app_mock.register_component.assert_called_with(var) assert core_mock.component_ids == [] @@ -77,6 +77,6 @@ async def test_register_component__with_setup_priority(monkeypatch): assert actual is var add_mock.assert_called() - assert add_mock.call_count == 3 + assert add_mock.call_count == 4 app_mock.register_component.assert_called_with(var) assert core_mock.component_ids == [] From ce29a3b07a71a7ba822c2c8cc09905a2c3784a31 Mon Sep 17 00:00:00 2001 From: puuu Date: Tue, 24 Aug 2021 10:18:40 +0900 Subject: [PATCH 141/285] mqtt_light: remove legacy API config that is not compatible with HA 2021.8 (#2183) --- esphome/components/mqtt/mqtt_light.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index be662867cf..b702e6e425 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -54,12 +54,6 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) root["brightness"] = true; - if (traits.supports_color_capability(ColorCapability::RGB)) - root["rgb"] = true; - if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) - root["color_temp"] = true; - if (traits.supports_color_capability(ColorCapability::WHITE)) - root["white_value"] = true; if (this->state_->supports_effects()) { root["effect"] = true; From eff626248f8be84601b3120514d972d3f27670a6 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 23 Aug 2021 20:20:39 -0500 Subject: [PATCH 142/285] Tuya fan component uses enum datapoint type for speed instead of integer (#2182) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/fan/tuya_fan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 8738b7f4a0..e9f8ce8e96 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -80,7 +80,7 @@ void TuyaFan::write_state() { } if (this->speed_id_.has_value()) { ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_integer_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); } } From e2640c8368684fef71f343c15498bf15198ddf3f Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 23 Aug 2021 18:26:59 -0700 Subject: [PATCH 143/285] Fix template select lambda (#2198) --- esphome/components/template/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 4044a407f3..3a707628a8 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -55,7 +55,7 @@ async def to_code(config): if CONF_LAMBDA in config: template_ = await cg.process_lambda( - config[CONF_LAMBDA], [], return_type=cg.optional.template(str) + config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) ) cg.add(var.set_template(template_)) From ed68a0e7734e8ff2841ac6ffdf438a35960ae774 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Mon, 23 Aug 2021 21:38:59 -0400 Subject: [PATCH 144/285] Internally all temperature units are Celsius so just send it directly (#1840) --- esphome/components/mqtt/mqtt_climate.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 5809b6616c..be9dbb0a08 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["max_temp"] = traits.get_visual_max_temperature(); // temp_step root["temp_step"] = traits.get_visual_temperature_step(); + // temperature units are always coerced to Celsius internally + root["temp_unit"] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic From 565473c90c4eaaeaec22fbccf34938c788edd7f0 Mon Sep 17 00:00:00 2001 From: Stephan Peijnik-Steinwender Date: Tue, 24 Aug 2021 12:57:53 +0200 Subject: [PATCH 145/285] ST7789V: Make backlight_pin optional (#2180) --- esphome/components/st7789v/display.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index a053d00ea2..7b38b1d2c5 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -29,7 +29,7 @@ CONFIG_SCHEMA = ( cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, } ) @@ -49,8 +49,9 @@ async def to_code(config): reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) cg.add(var.set_reset_pin(reset)) - bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) - cg.add(var.set_backlight_pin(bl)) + if CONF_BACKLIGHT_PIN in config: + bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) + cg.add(var.set_backlight_pin(bl)) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( From 39cd2838dfed9fcd3af5995f0c19bc0c444328b8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:38:51 +1200 Subject: [PATCH 146/285] Revert "Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2186)" (#2202) This reverts commit b0fa317302df4f6adeab56034e0b6099f58aa4c4. --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 77c377d39e..0f5b7b4b93 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), + BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 3be56fd50297fc461c2d5f7a9b19ccc850a016c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 26 Aug 2021 09:27:00 +1200 Subject: [PATCH 147/285] Fix SDM energy units to be KILO... (#2206) --- esphome/components/sdm_meter/sensor.py | 12 ++++++------ esphome/const.py | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index c3af47a664..ffb88af5ac 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -28,12 +28,12 @@ from esphome.const import ( UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + UNIT_KILOWATT_HOURS, UNIT_VOLT, UNIT_VOLT_AMPS, UNIT_VOLT_AMPS_REACTIVE, - UNIT_VOLT_AMPS_REACTIVE_HOURS, UNIT_WATT, - UNIT_WATT_HOURS, ) AUTO_LOAD = ["modbus"] @@ -101,28 +101,28 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, + unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, + unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, diff --git a/esphome/const.py b/esphome/const.py index d8134bc45f..932e7e8c61 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -768,6 +768,8 @@ UNIT_KELVIN = "K" UNIT_KILOGRAM = "kg" UNIT_KILOMETER = "km" UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh" +UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" UNIT_METER_PER_SECOND_SQUARED = "m/s²" From de871862a84a9c56627e00ddcb281b6e31216b43 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Thu, 26 Aug 2021 08:25:24 +1000 Subject: [PATCH 148/285] Optionally set direction on fan.turn_on action (#2171) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/fan/__init__.py | 13 +++++++++++++ esphome/components/fan/automation.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 9db2e9ed12..27dee3271f 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -18,6 +18,7 @@ from esphome.const import ( CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, + CONF_DIRECTION, ) from esphome.core import CORE, coroutine_with_priority @@ -27,6 +28,12 @@ fan_ns = cg.esphome_ns.namespace("fan") FanState = fan_ns.class_("FanState", cg.Nameable, cg.Component) MakeFan = cg.Application.struct("MakeFan") +FanDirection = fan_ns.enum("FanDirection") +FAN_DIRECTION_ENUM = { + "FORWARD": FanDirection.FAN_DIRECTION_FORWARD, + "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, +} + # Actions TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action) TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action) @@ -143,6 +150,9 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): cv.Required(CONF_ID): cv.use_id(FanState), cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), + cv.Optional(CONF_DIRECTION): cv.templatable( + cv.enum(FAN_DIRECTION_ENUM, upper=True) + ), } ), ) @@ -155,6 +165,9 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): if CONF_SPEED in config: template_ = await cg.templatable(config[CONF_SPEED], args, int) cg.add(var.set_speed(template_)) + if CONF_DIRECTION in config: + template_ = await cg.templatable(config[CONF_DIRECTION], args, FanDirection) + cg.add(var.set_direction(template_)) return var diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index fbfc71c720..29a8e8d992 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -13,6 +13,7 @@ template class TurnOnAction : public Action { TEMPLATABLE_VALUE(bool, oscillating) TEMPLATABLE_VALUE(int, speed) + TEMPLATABLE_VALUE(FanDirection, direction) void play(Ts... x) override { auto call = this->state_->turn_on(); @@ -22,6 +23,9 @@ template class TurnOnAction : public Action { if (this->speed_.has_value()) { call.set_speed(this->speed_.value(x...)); } + if (this->direction_.has_value()) { + call.set_direction(this->direction_.value(x...)); + } call.perform(); } From 94b28102f56d7bee37f87b0282a76dbd8c918b51 Mon Sep 17 00:00:00 2001 From: marsjan155 Date: Thu, 26 Aug 2021 04:33:03 +0200 Subject: [PATCH 149/285] Add st7920 display, (#1440) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/st7920/__init__.py | 0 esphome/components/st7920/display.py | 42 ++++++++ esphome/components/st7920/st7920.cpp | 146 ++++++++++++++++++++++++++ esphome/components/st7920/st7920.h | 50 +++++++++ tests/test1.yaml | 8 ++ 6 files changed, 247 insertions(+) create mode 100644 esphome/components/st7920/__init__.py create mode 100644 esphome/components/st7920/display.py create mode 100644 esphome/components/st7920/st7920.cpp create mode 100644 esphome/components/st7920/st7920.h diff --git a/CODEOWNERS b/CODEOWNERS index 1298d4d43d..eebaf02671 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -129,6 +129,7 @@ esphome/components/ssd1351_base/* @kbx81 esphome/components/ssd1351_spi/* @kbx81 esphome/components/st7735/* @SenexCrenshaw esphome/components/st7789v/* @kbx81 +esphome/components/st7920/* @marsjan155 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/switch/* @esphome/core diff --git a/esphome/components/st7920/__init__.py b/esphome/components/st7920/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/st7920/display.py b/esphome/components/st7920/display.py new file mode 100644 index 0000000000..9b544fa644 --- /dev/null +++ b/esphome/components/st7920/display.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import display, spi +from esphome.const import CONF_ID, CONF_LAMBDA, CONF_WIDTH, CONF_HEIGHT + +AUTO_LOAD = ["display"] +CODEOWNERS = ["@marsjan155"] +DEPENDENCIES = ["spi"] + +st7920_ns = cg.esphome_ns.namespace("st7920") +ST7920 = st7920_ns.class_( + "ST7920", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice +) +ST7920Ref = ST7920.operator("ref") + +CONFIG_SCHEMA = ( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(ST7920), + cv.Required(CONF_WIDTH): cv.int_, + cv.Required(CONF_HEIGHT): cv.int_, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(ST7920Ref, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + + await display.register_display(var, config) diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp new file mode 100644 index 0000000000..d985b0a426 --- /dev/null +++ b/esphome/components/st7920/st7920.cpp @@ -0,0 +1,146 @@ +#include "st7920.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7920 { + +static const char *const TAG = "st7920"; + +// ST7920 COMMANDS +static const uint8_t LCD_DATA = 0xFA; +static const uint8_t LCD_COMMAND = 0xF8; +static const uint8_t LCD_CLS = 0x01; +static const uint8_t LCD_HOME = 0x02; +static const uint8_t LCD_ADDRINC = 0x06; +static const uint8_t LCD_DISPLAYON = 0x0C; +static const uint8_t LCD_DISPLAYOFF = 0x08; +static const uint8_t LCD_CURSORON = 0x0E; +static const uint8_t LCD_CURSORBLINK = 0x0F; +static const uint8_t LCD_BASIC = 0x30; +static const uint8_t LCD_GFXMODE = 0x36; +static const uint8_t LCD_EXTEND = 0x34; +static const uint8_t LCD_TXTMODE = 0x34; +static const uint8_t LCD_STANDBY = 0x01; +static const uint8_t LCD_SCROLL = 0x03; +static const uint8_t LCD_SCROLLADDR = 0x40; +static const uint8_t LCD_ADDR = 0x80; +static const uint8_t LCD_LINE0 = 0x80; +static const uint8_t LCD_LINE1 = 0x90; +static const uint8_t LCD_LINE2 = 0x88; +static const uint8_t LCD_LINE3 = 0x98; + +void ST7920::setup() { + ESP_LOGCONFIG(TAG, "Setting up ST7920..."); + this->dump_config(); + this->spi_setup(); + this->init_internal_(this->get_buffer_length_()); + display_init_(); +} + +void ST7920::command_(uint8_t value) { + this->enable(); + this->send_(LCD_COMMAND, value); + this->disable(); +} + +void ST7920::data_(uint8_t value) { + this->enable(); + this->send_(LCD_DATA, value); + this->disable(); +} + +void ST7920::send_(uint8_t type, uint8_t value) { + this->write_byte(type); + this->write_byte(value & 0xF0); + this->write_byte(value << 4); +} + +void ST7920::goto_xy_(uint16_t x, uint16_t y) { + if (y >= 32 && y < 64) { + y -= 32; + x += 8; + } else if (y >= 64 && y < 64 + 32) { + y -= 32; + x += 0; + } else if (y >= 64 + 32 && y < 64 + 64) { + y -= 64; + x += 8; + } + this->command_(LCD_ADDR | y); // 6-bit (0..63) + this->command_(LCD_ADDR | x); // 4-bit (0..15) +} + +void HOT ST7920::write_display_data() { + uint8_t i, j, b; + for (j = 0; j < this->get_height_internal() / 2; j++) { + this->goto_xy_(0, j); + this->enable(); + for (i = 0; i < 16; i++) { // 16 bytes from line #0+ + b = this->buffer_[i + j * 16]; + this->send_(LCD_DATA, b); + } + for (i = 0; i < 16; i++) { // 16 bytes from line #32+ + b = this->buffer_[i + (j + 32) * 16]; + this->send_(LCD_DATA, b); + } + this->disable(); + App.feed_wdt(); + } +} + +void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } + +void ST7920::dump_config() { + LOG_DISPLAY("", "ST7920", this); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Height: %d", this->height_); + ESP_LOGCONFIG(TAG, " Width: %d", this->width_); +} + +float ST7920::get_setup_priority() const { return setup_priority::PROCESSOR; } + +void ST7920::update() { + this->clear(); + if (this->writer_local_.has_value()) // call lambda function if available + (*this->writer_local_)(*this); + this->write_display_data(); +} + +int ST7920::get_width_internal() { return this->width_; } + +int ST7920::get_height_internal() { return this->height_; } + +size_t ST7920::get_buffer_length_() { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; +} + +void HOT ST7920::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { + ESP_LOGW(TAG, "Position out of area: %dx%d", x, y); + return; + } + int width = this->get_width_internal() / 8u; + if (color.is_on()) { + this->buffer_[y * width + x / 8] |= (0x80 >> (x & 7)); + } else { + this->buffer_[y * width + x / 8] &= ~(0x80 >> (x & 7)); + } +} + +void ST7920::display_init_() { + ESP_LOGD(TAG, "Initializing display..."); + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_BASIC); // 8bit mode + this->command_(LCD_CLS); // clear screen + delay(12); // >10 ms delay + this->command_(LCD_ADDRINC); // cursor increment right no shift + this->command_(LCD_DISPLAYON); // D=1, C=0, B=0 + this->command_(LCD_EXTEND); // LCD_EXTEND); + this->command_(LCD_GFXMODE); // LCD_GFXMODE); + this->write_display_data(); +} + +} // namespace st7920 +} // namespace esphome diff --git a/esphome/components/st7920/st7920.h b/esphome/components/st7920/st7920.h new file mode 100644 index 0000000000..d0258d922c --- /dev/null +++ b/esphome/components/st7920/st7920.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace st7920 { + +class ST7920; + +using st7920_writer_t = std::function; + +class ST7920 : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_writer(st7920_writer_t &&writer) { this->writer_local_ = writer; } + void set_height(uint16_t height) { this->height_ = height; } + void set_width(uint16_t width) { this->width_ = width; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void fill(Color color) override; + void write_display_data(); + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + int get_height_internal() override; + int get_width_internal() override; + size_t get_buffer_length_(); + void display_init_(); + void command_(uint8_t value); + void data_(uint8_t value); + void send_(uint8_t type, uint8_t value); + void goto_xy_(uint16_t x, uint16_t y); + void start_transaction_(); + void end_transaction_(); + + int16_t width_ = 128, height_ = 64; + optional writer_local_{}; +}; + +} // namespace st7920 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index bcf5f932a8..a3f7a97281 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2038,6 +2038,14 @@ display: backlight_pin: GPIO4 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: st7920 + width: 128 + height: 64 + cs_pin: + number: GPIO23 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 model: 'INITR_BLACKTAB' cs_pin: GPIO5 From b955527f6ce3e6a7d8066112beb03b4b2e1b1e87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:34:39 +1200 Subject: [PATCH 150/285] Fix css/js file loading for webserver when esphome not executed form config directory (#2207) --- esphome/components/web_server/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index a181f83c64..ca3a60f43f 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -13,7 +13,7 @@ from esphome.const import ( CONF_USERNAME, CONF_PASSWORD, ) -from esphome.core import coroutine_with_priority +from esphome.core import CORE, coroutine_with_priority AUTO_LOAD = ["json", "web_server_base"] @@ -61,9 +61,11 @@ async def to_code(config): cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") - with open(config[CONF_CSS_INCLUDE], "r") as myfile: + path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) + with open(path, "r") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") - with open(config[CONF_JS_INCLUDE], "r") as myfile: + path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) + with open(path, "r") as myfile: cg.add(var.set_js_include(myfile.read())) From b5de43b2255e95d7e1348012755ed7b799e44e58 Mon Sep 17 00:00:00 2001 From: Alessandro Campolo Date: Sun, 29 Aug 2021 23:07:06 +0200 Subject: [PATCH 151/285] cs_pin made optional for ili9341 (#2219) --- esphome/components/ili9341/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ili9341/display.py b/esphome/components/ili9341/display.py index 450a958c56..157e8212bd 100644 --- a/esphome/components/ili9341/display.py +++ b/esphome/components/ili9341/display.py @@ -42,7 +42,7 @@ CONFIG_SCHEMA = cv.All( } ) .extend(cv.polling_component_schema("1s")) - .extend(spi.spi_device_schema()), + .extend(spi.spi_device_schema(False)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), ) From 56225701f9cdb13b052763190031f255fb909274 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 30 Aug 2021 09:27:38 +1200 Subject: [PATCH 152/285] Fix Packages when using MQTT (#2210) --- esphome/config_validation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3aebca81b8..61ef7d2f9f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1457,7 +1457,11 @@ class OnlyWith(Optional): if self._component in CORE.raw_config or ( CONF_PACKAGES in CORE.raw_config and self._component - in {list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()} + in [ + k + for package in CORE.raw_config[CONF_PACKAGES].values() + for k in package.keys() + ] ): return self._default return vol.UNDEFINED From fac49896dfb372e939f4bccade9a77be888a7faa Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 29 Aug 2021 23:41:05 +0200 Subject: [PATCH 153/285] Update known boards (#2190) --- esphome/boards.py | 277 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/esphome/boards.py b/esphome/boards.py index 220d440a37..ba6fe889ea 100644 --- a/esphome/boards.py +++ b/esphome/boards.py @@ -55,6 +55,7 @@ ESP8266_BOARD_PINS = { "espectro": {"LED": 15, "BUTTON": 2}, "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, "espinotee": {"LED": 16}, + "espmxdevkit": {}, "espresso_lite_v1": {"LED": 16}, "espresso_lite_v2": {"LED": 2}, "gen4iod": {}, @@ -105,6 +106,10 @@ ESP8266_BOARD_PINS = { }, "phoenix_v1": {"LED": 16}, "phoenix_v2": {"LED": 2}, + "sonoff_basic": {}, + "sonoff_s20": {}, + "sonoff_sv": {}, + "sonoff_th": {}, "sparkfunBlynk": "thing", "thing": {"LED": 5, "SDA": 2, "SCL": 14}, "thingdev": "thing", @@ -166,6 +171,7 @@ ESP8266_FLASH_SIZES = { "espectro": FLASH_SIZE_4_MB, "espino": FLASH_SIZE_4_MB, "espinotee": FLASH_SIZE_4_MB, + "espmxdevkit": FLASH_SIZE_1_MB, "espresso_lite_v1": FLASH_SIZE_4_MB, "espresso_lite_v2": FLASH_SIZE_4_MB, "gen4iod": FLASH_SIZE_512_KB, @@ -178,6 +184,10 @@ ESP8266_FLASH_SIZES = { "oak": FLASH_SIZE_4_MB, "phoenix_v1": FLASH_SIZE_4_MB, "phoenix_v2": FLASH_SIZE_4_MB, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_MB, "sparkfunBlynk": FLASH_SIZE_4_MB, "thing": FLASH_SIZE_512_KB, "thingdev": FLASH_SIZE_512_KB, @@ -291,6 +301,7 @@ ESP32_BOARD_PINS = { "SW2": 2, "SW3": 0, }, + "az-delivery-devkit-v4": {}, "bpi-bit": { "BUTTON_A": 35, "BUTTON_B": 27, @@ -320,6 +331,8 @@ ESP32_BOARD_PINS = { "RGB_LED": 4, "TEMPERATURE_SENSOR": 34, }, + "briki_abc_esp32": {}, + "briki_mbc-wb_esp32": {}, "d-duino-32": { "D1": 5, "D10": 1, @@ -380,11 +393,58 @@ ESP32_BOARD_PINS = { "esp32cam": {}, "esp32dev": {}, "esp32doit-devkit-v1": {"LED": 2}, + "esp32doit-espduino": {"TX0": 1, "RX0": 3, "CMD": 11, "CLK": 6, "SD0": 7, "SD1": 8}, "esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2}, + "esp32thing_plus": { + "SDA": 23, + "SCL": 22, + "SS": 33, + "MOSI": 18, + "MISO": 19, + "SCK": 5, + "A0": 26, + "A1": 25, + "A2": 34, + "A3": 39, + "A4": 36, + "A5": 4, + "A6": 14, + "A7": 32, + "A8": 15, + "A9": 33, + "A10": 27, + "A11": 12, + "A12": 13, + }, "esp32vn-iot-uno": {}, "espea32": {"BUTTON": 0, "LED": 5}, "espectro32": {"LED": 15, "SD_SS": 33}, "espino32": {"BUTTON": 0, "LED": 16}, + "etboard": { + "LED_BUILTIN": 5, + "TX": 34, + "RX": 35, + "SS": 29, + "MOSI": 37, + "MISO": 31, + "SCK": 30, + "A0": 36, + "A1": 39, + "A2": 32, + "A3": 33, + "A4": 34, + "A5": 35, + "A6": 25, + "A7": 26, + "D2": 27, + "D3": 14, + "D4": 12, + "D5": 13, + "D6": 15, + "D7": 16, + "D8": 17, + "D9": 4, + }, "featheresp32": { "A0": 26, "A1": 25, @@ -434,6 +494,18 @@ ESP32_BOARD_PINS = { "SW4": 21, }, "frogboard": {}, + "healtypi4": { + "KEY_BUILTIN": 17, + "ADS1292_DRDY_PIN": 26, + "ADS1292_CS_PIN": 13, + "ADS1292_START_PIN": 14, + "ADS1292_PWDN_PIN": 27, + "AFE4490_CS_PIN": 21, + "AFE4490_DRDY_PIN": 39, + "AFE4490_PWDN_PIN": 4, + "PUSH_BUTTON": 17, + "SLIDE_SWITCH": 16, + }, "heltec_wifi_kit_32": { "A1": 37, "A2": 38, @@ -444,6 +516,7 @@ ESP32_BOARD_PINS = { "SDA_OLED": 4, "Vext": 21, }, + "heltec_wifi_kit_32_v2": "heltec_wifi_kit_32", "heltec_wifi_lora_32": { "BUTTON": 0, "DIO0": 26, @@ -489,8 +562,68 @@ ESP32_BOARD_PINS = { "SS": 18, "Vext": 21, }, + "heltec_wireless_stick_lite": { + "LED_BUILTIN": 25, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "Vext": 21, + "LED": 25, + "RST_LoRa": 14, + "DIO0": 26, + "DIO1": 35, + "DIO2": 34, + }, + "honeylemon": { + "LED_BUILTIN": 2, + "BUILTIN_KEY": 0, + }, "hornbill32dev": {"BUTTON": 0, "LED": 13}, "hornbill32minima": {"SS": 2}, + "imbrios-logsens-v1p1": { + "LED_BUILTIN": 33, + "UART2_TX": 17, + "UART2_RX": 16, + "UART2_RTS": 4, + "CAN_TX": 17, + "CAN_RX": 16, + "CAN_TXDE": 4, + "SS": 15, + "MOSI": 13, + "MISO": 12, + "SCK": 14, + "SPI_SS1": 23, + "BUZZER_CTRL": 19, + "SD_CARD_DETECT": 35, + "SW2_BUILDIN": 0, + "SW3_BUILDIN": 36, + "SW4_BUILDIN": 34, + "LED1_BUILDIN": 32, + "LED2_BUILDIN": 33, + }, + "inex_openkb": { + "LED_BUILTIN": 16, + "LDR_PIN": 36, + "SW1": 16, + "SW2": 14, + "BT_LED": 17, + "WIFI_LED": 2, + "NTP_LED": 15, + "IOT_LED": 12, + "BUZZER": 13, + "INPUT1": 32, + "INPUT2": 33, + "INPUT3": 34, + "INPUT4": 35, + "OUTPUT1": 26, + "OUTPUT2": 27, + "SDA0": 21, + "SCL0": 22, + "SDA1": 4, + "SCL1": 5, + }, "intorobot": { "A1": 39, "A2": 35, @@ -528,6 +661,40 @@ ESP32_BOARD_PINS = { "iotaap_magnolia": {}, "iotbusio": {}, "iotbusproteus": {}, + "kits-edu": {}, + "labplus_mpython": { + "SDA": 23, + "SCL": 22, + "P0": 33, + "P1": 32, + "P2": 35, + "P3": 34, + "P4": 39, + "P5": 0, + "P6": 16, + "P7": 17, + "P8": 26, + "P9": 25, + "P10": 36, + "P11": 2, + "P13": 18, + "P14": 19, + "P15": 21, + "P16": 5, + "P19": 22, + "P20": 23, + "P": 27, + "Y": 14, + "T": 12, + "H": 13, + "O": 15, + "N": 4, + "BTN_A": 0, + "BTN_B": 2, + "SOUND": 36, + "LIGHT": 39, + "BUZZER": 16, + }, "lolin32": {"LED": 5}, "lolin32_lite": {"LED": 22}, "lolin_d32": {"LED": 5, "_VBAT": 35}, @@ -554,6 +721,16 @@ ESP32_BOARD_PINS = { "SDA": 12, "SS": 18, }, + "m5stack-atom": { + "SDA": 26, + "SCL": 32, + "ADC1": 35, + "ADC2": 36, + "SS": 19, + "MOSI": 33, + "MISO": 23, + "SCK": 22, + }, "m5stack-core-esp32": { "ADC1": 35, "ADC2": 36, @@ -580,6 +757,26 @@ ESP32_BOARD_PINS = { "RXD2": 16, "TXD2": 17, }, + "m5stack-core2": { + "SDA": 32, + "SCL": 33, + "SS": 5, + "MOSI": 23, + "MISO": 38, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, + "m5stack-coreink": { + "SDA": 32, + "SCL": 33, + "SS": 9, + "MOSI": 23, + "MISO": 34, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, "m5stack-fire": { "ADC1": 35, "ADC2": 36, @@ -630,6 +827,17 @@ ESP32_BOARD_PINS = { "RXD2": 16, "TXD2": 17, }, + "m5stack-timer-cam": { + "LED_BUILTIN": 2, + "SDA": 4, + "SCL": 13, + "SS": 5, + "MOSI": 23, + "MISO": 19, + "SCK": 18, + "ADC1": 35, + "ADC2": 36, + }, "m5stick-c": { "ADC1": 35, "ADC2": 36, @@ -664,6 +872,17 @@ ESP32_BOARD_PINS = { "RIGHT_PUTTON": 34, "YELLOW_LED": 18, }, + "mgbot-iotik32a": { + "LED_BUILTIN": 4, + "TX2": 17, + "RX2": 16, + }, + "mgbot-iotik32b": { + "LED_BUILTIN": 18, + "IR": 27, + "TX2": 17, + "RX2": 16, + }, "mhetesp32devkit": {"LED": 2}, "mhetesp32minikit": {"LED": 2}, "microduino-core-esp32": { @@ -740,6 +959,7 @@ ESP32_BOARD_PINS = { }, "node32s": {}, "nodemcu-32s": {"BUTTON": 0, "LED": 2}, + "nscreen-32": {}, "odroid_esp32": {"ADC1": 35, "ADC2": 36, "LED": 2, "SCL": 4, "SDA": 15, "SS": 22}, "onehorse32dev": {"A1": 37, "A2": 38, "BUTTON": 0, "LED": 5}, "oroca_edubot": { @@ -766,6 +986,10 @@ ESP32_BOARD_PINS = { "VBAT": 35, }, "pico32": {}, + "piranha_esp32": { + "LED_BUILTIN": 2, + "KEY_BUILTIN": 0, + }, "pocket_32": {"LED": 16}, "pycom_gpy": { "A1": 37, @@ -778,7 +1002,14 @@ ESP32_BOARD_PINS = { "SDA": 12, "SS": 17, }, + "qchip": "heltec_wifi_kit_32", "quantum": {}, + "s_odi_ultra": { + "LED_BUILTIN": 2, + "LED_BUILTINB": 4, + }, + "sensesiot_weizen": {}, + "sg-o_airMon": {}, "sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16}, "tinypico": {}, "ttgo-lora32-v1": { @@ -790,6 +1021,26 @@ ESP32_BOARD_PINS = { "SCK": 5, "SS": 18, }, + "ttgo-lora32-v2": { + "LED_BUILTIN": 22, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "A1": 37, + "A2": 38, + }, + "ttgo-lora32-v21": { + "LED_BUILTIN": 25, + "KEY_BUILTIN": 0, + "SS": 18, + "MOSI": 27, + "MISO": 19, + "SCK": 5, + "A1": 37, + "A2": 38, + }, "ttgo-t-beam": {"BUTTON": 39, "LED": 14, "MOSI": 27, "SCK": 5, "SS": 18}, "ttgo-t-watch": {"BUTTON": 36, "MISO": 2, "MOSI": 15, "SCK": 14, "SS": 13}, "ttgo-t1": {"LED": 22, "MISO": 2, "MOSI": 15, "SCK": 14, "SCL": 23, "SS": 13}, @@ -855,6 +1106,32 @@ ESP32_BOARD_PINS = { "T5": 5, "T6": 4, }, + "wifiduino32": { + "LED_BUILTIN": 2, + "KEY_BUILTIN": 0, + "SDA": 5, + "SCL": 16, + "A0": 27, + "A1": 14, + "A2": 12, + "A3": 35, + "A4": 13, + "A5": 4, + "D0": 3, + "D1": 1, + "D2": 17, + "D3": 15, + "D4": 32, + "D5": 33, + "D6": 25, + "D7": 26, + "D8": 23, + "D9": 22, + "D10": 21, + "D11": 19, + "D12": 18, + "D13": 2, + }, "xinabox_cw02": {"LED": 27}, } From f923ba87c0c55e219e55993e13596b2a60724d37 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 30 Aug 2021 09:41:14 +1200 Subject: [PATCH 154/285] Bump dashboard to 20210826.0 (#2211) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b4d557f06e..79fefbfcaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==3.1 click==7.1.2 -esphome-dashboard==20210728.0 +esphome-dashboard==20210826.0 From 9218e85bd68c1fab554f8515cec172a9f2faec38 Mon Sep 17 00:00:00 2001 From: Marcio Granzotto Rodrigues Date: Mon, 30 Aug 2021 15:03:30 -0300 Subject: [PATCH 155/285] Remove footer validation for fujitsu_general (#2196) --- esphome/components/fujitsu_general/fujitsu_general.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 8d789bbcfc..9e58f672c7 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -297,12 +297,6 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { } } - // Validate footer - if (!data.expect_mark(FUJITSU_GENERAL_BIT_MARK)) { - ESP_LOGV(TAG, "Footer fail"); - return false; - } - for (uint8_t byte = 0; byte < recv_message_length; ++byte) { ESP_LOGVV(TAG, "%02X", recv_message[byte]); } From 37f322585e16438e95da8ce9bde6d65c2be54103 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Mon, 30 Aug 2021 15:48:19 -0300 Subject: [PATCH 156/285] Glmnet schema 202105 (#2220) --- script/build_jsonschema.py | 45 +++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 1ab0ffa015..7a3257411c 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -25,6 +25,11 @@ JSC_DESCRIPTION = "description" JSC_ONEOF = "oneOf" JSC_PROPERTIES = "properties" JSC_REF = "$ref" + +# this should be required, but YAML Language server completion does not work properly if required are specified. +# still needed for other features / checks +JSC_REQUIRED = "required_" + SIMPLE_AUTOMATION = "simple_automation" schema_names = {} @@ -295,9 +300,17 @@ def get_automation_schema(name, vschema): # * an object with automation's schema and a then key # with again a single action or an array of actions + if len(extra_jschema[JSC_PROPERTIES]) == 0: + return get_ref(SIMPLE_AUTOMATION) + extra_jschema[JSC_PROPERTIES]["then"] = add_definition_array_or_single_object( get_ref(JSC_ACTION) ) + # if there is a required element in extra_jschema then this automation does not support + # directly a list of actions + if JSC_REQUIRED in extra_jschema: + return create_ref(name, extra_vschema, extra_jschema) + jschema = add_definition_array_or_single_object(get_ref(JSC_ACTION)) jschema[JSC_ANYOF].append(extra_jschema) @@ -370,9 +383,14 @@ def get_entry(parent_key, vschema): # everything else just accept string and let ESPHome validate try: from esphome.core import ID + from esphome.automation import Trigger, Automation v = vschema(None) if isinstance(v, ID): + if v.type.base != "script::Script" and ( + v.type.inherits_from(Trigger) or v.type == Automation + ): + return None entry = {"type": "string", "id_type": v.type.base} elif isinstance(v, str): entry = {"type": "string"} @@ -494,9 +512,11 @@ def convert_schema(path, vschema, un_extend=True): output = {} if str(vschema) in ejs.hidden_schemas: - # this can get another think twist. When adding this I've already figured out - # interval and script in other way - if path not in ["interval", "script"]: + if ejs.hidden_schemas[str(vschema)] == "automation": + vschema = vschema(ejs.jschema_extractor) + jschema = get_jschema(path, vschema, True) + return add_definition_array_or_single_object(jschema) + else: vschema = vschema(ejs.jschema_extractor) if un_extend: @@ -515,9 +535,8 @@ def convert_schema(path, vschema, un_extend=True): return rhs # merge - if JSC_ALLOF in lhs and JSC_ALLOF in rhs: - output = lhs[JSC_ALLOF] + output = lhs for k in rhs[JSC_ALLOF]: merge(output[JSC_ALLOF], k) elif JSC_ALLOF in lhs: @@ -574,6 +593,7 @@ def convert_schema(path, vschema, un_extend=True): return output props = output[JSC_PROPERTIES] = {} + required = [] output["type"] = ["object", "null"] if DUMP_COMMENTS: @@ -616,13 +636,21 @@ def convert_schema(path, vschema, un_extend=True): if prop: # Deprecated (cv.Invalid) properties not added props[str(k)] = prop # TODO: see required, sometimes completions doesn't show up because of this... - # if isinstance(k, cv.Required): - # required.append(str(k)) + if isinstance(k, cv.Required): + required.append(str(k)) try: if str(k.default) != "...": - prop["default"] = k.default() + default_value = k.default() + # Yaml validator fails if `"default": null` ends up in the json schema + if default_value is not None: + if prop["type"] == "string": + default_value = str(default_value) + prop["default"] = default_value except: pass + + if len(required) > 0: + output[JSC_REQUIRED] = required return output @@ -648,6 +676,7 @@ def add_pin_registry(): internal = definitions[schema_name] definitions[schema_name]["additionalItems"] = False definitions[f"PIN.{mode}_INTERNAL"] = internal + internal[JSC_PROPERTIES]["number"] = {"type": ["number", "string"]} schemas = [get_ref(f"PIN.{mode}_INTERNAL")] schemas[0]["required"] = ["number"] # accept string and object, for internal shorthand pin IO: From 03190611bbf399b32f911c0f5444f3fb97bda9dc Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Tue, 31 Aug 2021 08:10:22 +1000 Subject: [PATCH 157/285] Add H-Bridge fan component (#2212) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/hbridge/__init__.py | 3 + esphome/components/hbridge/fan/__init__.py | 70 +++++++++++++++ .../components/hbridge/fan/hbridge_fan.cpp | 85 +++++++++++++++++++ esphome/components/hbridge/fan/hbridge_fan.h | 58 +++++++++++++ .../hbridge/{light.py => light/__init__.py} | 4 +- .../{ => light}/hbridge_light_output.h | 0 esphome/const.py | 1 + 8 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 esphome/components/hbridge/fan/__init__.py create mode 100644 esphome/components/hbridge/fan/hbridge_fan.cpp create mode 100644 esphome/components/hbridge/fan/hbridge_fan.h rename esphome/components/hbridge/{light.py => light/__init__.py} (94%) rename esphome/components/hbridge/{ => light}/hbridge_light_output.h (100%) diff --git a/CODEOWNERS b/CODEOWNERS index eebaf02671..53669b8b99 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,8 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/havells_solar/* @sourabhjaiswal +esphome/components/hbridge/fan/* @WeekendWarrior +esphome/components/hbridge/light/* @DotNetDann esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey diff --git a/esphome/components/hbridge/__init__.py b/esphome/components/hbridge/__init__.py index e69de29bb2..7eae863ff5 100644 --- a/esphome/components/hbridge/__init__.py +++ b/esphome/components/hbridge/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +hbridge_ns = cg.esphome_ns.namespace("hbridge") diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py new file mode 100644 index 0000000000..b169978acd --- /dev/null +++ b/esphome/components/hbridge/fan/__init__.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id +from esphome.components import fan, output +from esphome.const import ( + CONF_ID, + CONF_DECAY_MODE, + CONF_SPEED_COUNT, + CONF_PIN_A, + CONF_PIN_B, + CONF_ENABLE_PIN, +) +from .. import hbridge_ns + + +CODEOWNERS = ["@WeekendWarrior"] + + +HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState) + +DecayMode = hbridge_ns.enum("DecayMode") +DECAY_MODE_OPTIONS = { + "SLOW": DecayMode.DECAY_MODE_SLOW, + "FAST": DecayMode.DECAY_MODE_FAST, +} + +# Actions +BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action) + + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan), + cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), + cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), + cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum( + DECAY_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput), + } +).extend(cv.COMPONENT_SCHEMA) + + +@automation.register_action( + "fan.hbridge.brake", + BrakeAction, + maybe_simple_id({cv.Required(CONF_ID): cv.use_id(HBridgeFan)}), +) +async def fan_hbridge_brake_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_SPEED_COUNT], + config[CONF_DECAY_MODE], + ) + await fan.register_fan(var, config) + pin_a_ = await cg.get_variable(config[CONF_PIN_A]) + cg.add(var.set_pin_a(pin_a_)) + pin_b_ = await cg.get_variable(config[CONF_PIN_B]) + cg.add(var.set_pin_b(pin_b_)) + + if CONF_ENABLE_PIN in config: + enable_pin = await cg.get_variable(config[CONF_ENABLE_PIN]) + cg.add(var.set_enable_pin(enable_pin)) diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp new file mode 100644 index 0000000000..a4e5429ff4 --- /dev/null +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -0,0 +1,85 @@ +#include "hbridge_fan.h" +#include "esphome/components/fan/fan_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace hbridge { + +static const char *const TAG = "fan.hbridge"; + +void HBridgeFan::set_hbridge_levels_(float a_level, float b_level) { + this->pin_a_->set_level(a_level); + this->pin_b_->set_level(b_level); + ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f", a_level, b_level); +} + +// constant IN1/IN2, PWM on EN => power control, fast current decay +// constant IN1/EN, PWM on IN2 => power control, slow current decay +void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) { + this->pin_a_->set_level(a_level); + this->pin_b_->set_level(b_level); + this->enable_->set_level(enable); + ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable); +} + +fan::FanStateCall HBridgeFan::brake() { + ESP_LOGD(TAG, "Braking"); + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f); + return this->make_call().set_state(false); +} + +void HBridgeFan::dump_config() { + ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str()); + if (this->get_traits().supports_oscillation()) { + ESP_LOGCONFIG(TAG, " Oscillation: YES"); + } + if (this->get_traits().supports_direction()) { + ESP_LOGCONFIG(TAG, " Direction: YES"); + } + if (this->decay_mode_ == DECAY_MODE_SLOW) { + ESP_LOGCONFIG(TAG, " Decay Mode: Slow"); + } else { + ESP_LOGCONFIG(TAG, " Decay Mode: Fast"); + } +} +void HBridgeFan::setup() { + auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); + this->set_traits(traits); + this->add_on_state_callback([this]() { this->next_update_ = true; }); +} +void HBridgeFan::loop() { + if (!this->next_update_) { + return; + } + this->next_update_ = false; + + float speed = 0.0f; + if (this->state) { + speed = static_cast(this->speed) / static_cast(this->speed_count_); + } + if (speed == 0.0f) { // off means idle + (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed) + : this->set_hbridge_levels_(speed, speed, speed); + return; + } + if (this->direction == fan::FAN_DIRECTION_FORWARD) { + if (this->decay_mode_ == DECAY_MODE_SLOW) { + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f) + : this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f); + } else { // DECAY_MODE_FAST + (this->enable_ == nullptr) ? this->set_hbridge_levels_(0.0f, speed) + : this->set_hbridge_levels_(0.0f, 1.0f, speed); + } + } else { // fan::FAN_DIRECTION_REVERSE + if (this->decay_mode_ == DECAY_MODE_SLOW) { + (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f - speed) + : this->set_hbridge_levels_(1.0f, 1.0f - speed, 1.0f); + } else { // DECAY_MODE_FAST + (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, 0.0f) + : this->set_hbridge_levels_(1.0f, 0.0f, speed); + } + } +} + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h new file mode 100644 index 0000000000..984318c8d6 --- /dev/null +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/fan/fan_state.h" + +namespace esphome { +namespace hbridge { + +enum DecayMode { + DECAY_MODE_SLOW = 0, + DECAY_MODE_FAST = 1, +}; + +class HBridgeFan : public fan::FanState { + public: + HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {} + + void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; } + void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; } + void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } + + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + fan::FanStateCall brake(); + + int get_speed_count() { return this->speed_count_; } + // update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping + void internal_update() { this->next_update_ = true; } + + protected: + output::FloatOutput *pin_a_; + output::FloatOutput *pin_b_; + output::FloatOutput *enable_{nullptr}; + output::BinaryOutput *oscillating_{nullptr}; + bool next_update_{true}; + int speed_count_{}; + DecayMode decay_mode_{DECAY_MODE_SLOW}; + + void set_hbridge_levels_(float a_level, float b_level); + void set_hbridge_levels_(float a_level, float b_level, float enable); +}; + +template class BrakeAction : public Action { + public: + explicit BrakeAction(HBridgeFan *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->brake(); } + + HBridgeFan *parent_; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/light.py b/esphome/components/hbridge/light/__init__.py similarity index 94% rename from esphome/components/hbridge/light.py rename to esphome/components/hbridge/light/__init__.py index b4ae45977a..fe5c3e9845 100644 --- a/esphome/components/hbridge/light.py +++ b/esphome/components/hbridge/light/__init__.py @@ -2,8 +2,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B +from .. import hbridge_ns + +CODEOWNERS = ["@DotNetDann"] -hbridge_ns = cg.esphome_ns.namespace("hbridge") HBridgeLightOutput = hbridge_ns.class_( "HBridgeLightOutput", cg.PollingComponent, light.LightOutput ) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/light/hbridge_light_output.h similarity index 100% rename from esphome/components/hbridge/hbridge_light_output.h rename to esphome/components/hbridge/light/hbridge_light_output.h diff --git a/esphome/const.py b/esphome/const.py index 932e7e8c61..011b5deddb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -165,6 +165,7 @@ CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" +CONF_DECAY_MODE = "decay_mode" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" From f186ff8b46137744fd4892ef24716c37cb52c2db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:40:06 +1200 Subject: [PATCH 158/285] Bump black from 21.7b0 to 21.8b0 (#2222) Bumps [black](https://github.com/psf/black) from 21.7b0 to 21.8b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 684582bd4c..005131f315 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.9.6 flake8==3.9.2 -black==21.7b0 +black==21.8b0 pexpect==4.8.0 pre-commit From 58350b6c996c7e99bdfd73b7c4a07454fcdbadfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 11:10:52 +1200 Subject: [PATCH 159/285] Bump pytest from 6.2.4 to 6.2.5 (#2223) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 005131f315..b0de4b727b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pexpect==4.8.0 pre-commit # Unit tests -pytest==6.2.4 +pytest==6.2.5 pytest-cov==2.12.1 pytest-mock==3.6.1 pytest-asyncio==0.15.1 From 140ef791aa255ff2349bee8667b16d71cac4fb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 30 Aug 2021 22:00:30 -0400 Subject: [PATCH 160/285] Support for the AirThings Wave Plus (#1656) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 + esphome/components/airthings_ble/__init__.py | 23 +++ .../airthings_ble/airthings_listener.cpp | 33 ++++ .../airthings_ble/airthings_listener.h | 20 +++ .../airthings_wave_plus/__init__.py | 1 + .../airthings_wave_plus.cpp | 142 ++++++++++++++++++ .../airthings_wave_plus/airthings_wave_plus.h | 79 ++++++++++ .../components/airthings_wave_plus/sensor.py | 116 ++++++++++++++ esphome/const.py | 4 + tests/test2.yaml | 25 +++ 10 files changed, 445 insertions(+) create mode 100644 esphome/components/airthings_ble/__init__.py create mode 100644 esphome/components/airthings_ble/airthings_listener.cpp create mode 100644 esphome/components/airthings_ble/airthings_listener.h create mode 100644 esphome/components/airthings_wave_plus/__init__.py create mode 100644 esphome/components/airthings_wave_plus/airthings_wave_plus.cpp create mode 100644 esphome/components/airthings_wave_plus/airthings_wave_plus.h create mode 100644 esphome/components/airthings_wave_plus/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 53669b8b99..40bf27aa43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,8 @@ esphome/core/* @esphome/core esphome/components/ac_dimmer/* @glmnet esphome/components/adc/* @esphome/core esphome/components/addressable_light/* @justfalter +esphome/components/airthings_ble/* @jeromelaban +esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix esphome/components/animation/* @syndlex diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py new file mode 100644 index 0000000000..ca94069703 --- /dev/null +++ b/esphome/components/airthings_ble/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +DEPENDENCIES = ["esp32_ble_tracker"] +CODEOWNERS = ["@jeromelaban"] + +airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble") +AirthingsListener = airthings_ble_ns.class_( + "AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp new file mode 100644 index 0000000000..921e42c498 --- /dev/null +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -0,0 +1,33 @@ +#include "airthings_listener.h" +#include "esphome/core/log.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace airthings_ble { + +static const char *TAG = "airthings_ble"; + +bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + for (auto &it : device.get_manufacturer_datas()) { + if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) { + if (it.data.size() < 4) + continue; + + uint32_t sn = it.data[0]; + sn |= ((uint32_t) it.data[1] << 8); + sn |= ((uint32_t) it.data[2] << 16); + sn |= ((uint32_t) it.data[3] << 24); + + ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); + return true; + } + } + + return false; +} + +} // namespace airthings_ble +} // namespace esphome + +#endif diff --git a/esphome/components/airthings_ble/airthings_listener.h b/esphome/components/airthings_ble/airthings_listener.h new file mode 100644 index 0000000000..cd240ac1ba --- /dev/null +++ b/esphome/components/airthings_ble/airthings_listener.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef ARDUINO_ARCH_ESP32 + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include + +namespace esphome { +namespace airthings_ble { + +class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace airthings_ble +} // namespace esphome + +#endif diff --git a/esphome/components/airthings_wave_plus/__init__.py b/esphome/components/airthings_wave_plus/__init__.py new file mode 100644 index 0000000000..1aff461edd --- /dev/null +++ b/esphome/components/airthings_wave_plus/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jeromelaban"] diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp new file mode 100644 index 0000000000..6b2e807e0b --- /dev/null +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -0,0 +1,142 @@ +#include "airthings_wave_plus.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace airthings_wave_plus { + +void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "Connected successfully!"); + } + break; + } + + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "Disconnected!"); + break; + } + + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle = 0; + auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), + sensors_data_characteristic_uuid.to_string().c_str()); + break; + } + this->handle = chr->handle; + this->node_state = espbt::ClientState::Established; + + request_read_values_(); + break; + } + + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->conn_id) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } + if (param->read.handle == this->handle) { + read_sensors_(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { + auto value = (WavePlusReadings *) raw_value; + + if (sizeof(WavePlusReadings) <= value_len) { + ESP_LOGD(TAG, "version = %d", value->version); + + if (value->version == 1) { + ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); + + this->humidity_sensor_->publish_state(value->humidity / 2.0f); + if (is_valid_radon_value_(value->radon)) { + this->radon_sensor_->publish_state(value->radon); + } + if (is_valid_radon_value_(value->radon_lt)) { + this->radon_long_term_sensor_->publish_state(value->radon_lt); + } + this->temperature_sensor_->publish_state(value->temperature / 100.0f); + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + if (is_valid_co2_value_(value->co2)) { + this->co2_sensor_->publish_state(value->co2); + } + if (is_valid_voc_value_(value->voc)) { + this->tvoc_sensor_->publish_state(value->voc); + } + + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + parent()->set_enabled(false); + } else { + ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); + } + } +} + +bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } + +bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } + +bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } + +void AirthingsWavePlus::loop() {} + +void AirthingsWavePlus::update() { + if (this->node_state != espbt::ClientState::Established) { + if (!parent()->enabled) { + ESP_LOGW(TAG, "Reconnecting to device"); + parent()->set_enabled(true); + parent()->connect(); + } else { + ESP_LOGW(TAG, "Connection in progress"); + } + } +} + +void AirthingsWavePlus::request_read_values_() { + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + } +} + +void AirthingsWavePlus::dump_config() { + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "Radon", this->radon_sensor_); + LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); +} + +AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { + auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); + auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); + + service_uuid = espbt::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt); +} + +void AirthingsWavePlus::setup() {} + +} // namespace airthings_wave_plus +} // namespace esphome + +#endif // ARDUINO_ARCH_ESP32 diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h new file mode 100644 index 0000000000..18d7fe60d2 --- /dev/null +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -0,0 +1,79 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/log.h" +#include +#include + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include + +using namespace esphome::ble_client; + +namespace esphome { +namespace airthings_wave_plus { + +static const char *TAG = "airthings_wave_plus"; + +class AirthingsWavePlus : public PollingComponent, public BLEClientNode { + public: + AirthingsWavePlus(); + + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } + void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } + void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } + void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } + void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + + protected: + bool is_valid_radon_value_(short radon); + bool is_valid_voc_value_(short voc); + bool is_valid_co2_value_(short co2); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void request_read_values_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *radon_sensor_{nullptr}; + sensor::Sensor *radon_long_term_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *co2_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + + uint16_t handle; + espbt::ESPBTUUID service_uuid; + espbt::ESPBTUUID sensors_data_characteristic_uuid; + + struct WavePlusReadings { + uint8_t version; + uint8_t humidity; + uint8_t ambientLight; + uint8_t unused01; + uint16_t radon; + uint16_t radon_lt; + uint16_t temperature; + uint16_t pressure; + uint16_t co2; + uint16_t voc; + }; +}; + +} // namespace airthings_wave_plus +} // namespace esphome + +#endif // ARDUINO_ARCH_ESP32 diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py new file mode 100644 index 0000000000..4109fca700 --- /dev/null +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -0,0 +1,116 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client + +from esphome.const import ( + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + ICON_RADIOACTIVE, + CONF_ID, + CONF_RADON, + CONF_RADON_LONG_TERM, + CONF_HUMIDITY, + CONF_TVOC, + CONF_CO2, + CONF_PRESSURE, + CONF_TEMPERATURE, + UNIT_BECQUEREL_PER_CUBIC_METER, + UNIT_PARTS_PER_MILLION, + UNIT_PARTS_PER_BILLION, + ICON_RADIATOR, +) + +DEPENDENCIES = ["ble_client"] + +airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus") +AirthingsWavePlus = airthings_wave_plus_ns.class_( + "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode +) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(AirthingsWavePlus), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, + ), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5mins")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_RADON in config: + sens = await sensor.new_sensor(config[CONF_RADON]) + cg.add(var.set_radon(sens)) + if CONF_RADON_LONG_TERM in config: + sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) + cg.add(var.set_radon_long_term(sens)) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + if CONF_CO2 in config: + sens = await sensor.new_sensor(config[CONF_CO2]) + cg.add(var.set_co2(sens)) + if CONF_TVOC in config: + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) diff --git a/esphome/const.py b/esphome/const.py index 011b5deddb..579d8ef4c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -507,6 +507,8 @@ CONF_PROTOCOL = "protocol" CONF_PULL_MODE = "pull_mode" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" +CONF_RADON = "radon" +CONF_RADON_LONG_TERM = "radon_long_term" CONF_RANDOM = "random" CONF_RANGE = "range" CONF_RANGE_FROM = "range_from" @@ -732,6 +734,7 @@ ICON_PERCENT = "mdi:percent" ICON_POWER = "mdi:power" ICON_PULSE = "mdi:pulse" ICON_RADIATOR = "mdi:radiator" +ICON_RADIOACTIVE = "mdi:radioactive" ICON_RESTART = "mdi:restart" ICON_ROTATE_RIGHT = "mdi:rotate-right" ICON_RULER = "mdi:ruler" @@ -753,6 +756,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy" ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" +UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" UNIT_CELSIUS = "°C" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" diff --git a/tests/test2.yaml b/tests/test2.yaml index 6807278c0d..0db47965fe 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -246,6 +246,24 @@ sensor: id: freezer_temp_source reference_voltage: 3.19 number: 0 + - platform: airthings_wave_plus + ble_client_id: airthings01 + update_interval: 5min + temperature: + name: "Wave Plus Temperature" + radon: + name: "Wave Plus Radon" + radon_long_term: + name: "Wave Plus Radon Long Term" + pressure: + name: "Wave Plus Pressure" + humidity: + name: "Wave Plus Humidity" + co2: + name: "Wave Plus CO2" + tvoc: + name: "Wave Plus VOC" + time: - platform: homeassistant on_time: @@ -334,6 +352,12 @@ esp32_ble_tracker: - lambda: !lambda |- ESP_LOGD("main", "Length of manufacturer data is %i", x.size()); +ble_client: + - mac_address: 01:02:03:04:05:06 + id: airthings01 + +airthings_ble: + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' @@ -431,3 +455,4 @@ interval: - logger.log: 'Interval Run' display: + From 54337befc262f7935b61c2b9f970db0c66c8d74f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 31 Aug 2021 14:00:58 +1200 Subject: [PATCH 161/285] Fix some lint errors in pylint 2.10.2 (#2226) --- esphome/components/mcp23xxx_base/__init__.py | 3 ++- esphome/components/web_server/__init__.py | 4 ++-- esphome/config_validation.py | 7 ++++--- esphome/dashboard/dashboard.py | 6 ++++-- esphome/helpers.py | 10 +++++----- esphome/writer.py | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index 019b7c7e64..c22d377b3c 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -91,7 +91,7 @@ async def mcp23xxx_pin_to_code(config): # BEGIN Removed pin schemas below to show error in configuration -# TODO remove in 1.19.0 +# TODO remove in 2022.5.0 for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: PIN_SCHEMA = cv.Schema( @@ -110,6 +110,7 @@ for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: } ) + # pylint: disable=cell-var-from-loop @pins.PIN_SCHEMA_REGISTRY.register(id, (PIN_SCHEMA, PIN_SCHEMA)) def pin_to_code(config): pass diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ca3a60f43f..7f17767657 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -62,10 +62,10 @@ async def to_code(config): if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(path, "r") as myfile: + with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: cg.add_define("WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(path, "r") as myfile: + with open(file=path, mode="r", encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.read())) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 61ef7d2f9f..4df65a38e3 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -836,10 +836,11 @@ pressure = float_with_unit("pressure", "(bar|Bar)", optional_unit=True) def temperature(value): + err = None try: return _temperature_c(value) - except Invalid as orig_err: # noqa - pass + except Invalid as orig_err: + err = orig_err try: kelvin = _temperature_k(value) @@ -853,7 +854,7 @@ def temperature(value): except Invalid: pass - raise orig_err # noqa + raise err _color_temperature_mireds = float_with_unit("Color Temperature", r"(mireds|Mireds)") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ea55ea3b18..8016bf7bbd 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -600,7 +600,7 @@ class EditRequestHandler(BaseHandler): content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(filename, "r") as f: + with open(file=filename, mode="r", encoding="utf-8") as f: content = f.read() self.write(content) @@ -608,7 +608,9 @@ class EditRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): # pylint: disable=no-value-for-parameter - with open(settings.rel_path(configuration), "wb") as f: + with open( + file=settings.rel_path(configuration), mode="wb", encoding="utf-8" + ) as f: f.write(self.request.body) self.set_status(200) diff --git a/esphome/helpers.py b/esphome/helpers.py index ad7b8272b2..c766cc1ac7 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -276,11 +276,11 @@ def file_compare(path1: os.PathLike, path2: os.PathLike) -> bool: # A dict of types that need to be converted to heaptypes before a class can be added # to the object _TYPE_OVERLOADS = { - int: type("EInt", (int,), dict()), - float: type("EFloat", (float,), dict()), - str: type("EStr", (str,), dict()), - dict: type("EDict", (str,), dict()), - list: type("EList", (list,), dict()), + int: type("EInt", (int,), {}), + float: type("EFloat", (float,), {}), + str: type("EStr", (str,), {}), + dict: type("EDict", (str,), {}), + list: type("EList", (list,), {}), } # cache created classes here diff --git a/esphome/writer.py b/esphome/writer.py index 641ae9b3cc..09ed284173 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -481,5 +481,5 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome def write_gitignore(): path = CORE.relative_config_path(".gitignore") if not os.path.isfile(path): - with open(path, "w") as f: + with open(file=path, mode="w", encoding="utf-8") as f: f.write(GITIGNORE_CONTENT) From ea1b5e19f048d4cc50ad81d14390395fc31c4d9a Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Mon, 30 Aug 2021 21:18:16 -0500 Subject: [PATCH 162/285] Add transitions to light flash (#2201) --- esphome/components/light/__init__.py | 8 ++++ esphome/components/light/light_state.cpp | 7 ++- esphome/components/light/light_state.h | 7 +++ esphome/components/light/transformers.h | 43 ++++++++++++++++++- .../components/power_supply/power_supply.cpp | 2 +- esphome/const.py | 1 + 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 52e8984545..69cb87e539 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_DEFAULT_TRANSITION_LENGTH, CONF_DISABLED_BY_DEFAULT, CONF_EFFECTS, + CONF_FLASH_TRANSITION_LENGTH, CONF_GAMMA_CORRECT, CONF_ID, CONF_INTERNAL, @@ -85,6 +86,9 @@ BRIGHTNESS_ONLY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( cv.Optional( CONF_DEFAULT_TRANSITION_LENGTH, default="1s" ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_FLASH_TRANSITION_LENGTH, default="0s" + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_EFFECTS): validate_effects(MONOCHROMATIC_EFFECTS), } ) @@ -132,6 +136,10 @@ async def setup_light_core_(light_var, output_var, config): config[CONF_DEFAULT_TRANSITION_LENGTH] ) ) + if CONF_FLASH_TRANSITION_LENGTH in config: + cg.add( + light_var.set_flash_transition_length(config[CONF_FLASH_TRANSITION_LENGTH]) + ) if CONF_GAMMA_CORRECT in config: cg.add(light_var.set_gamma_correct(config[CONF_GAMMA_CORRECT])) effects = await cg.build_registry_list( diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 4c4eefdc30..945d3910d5 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -157,6 +157,11 @@ void LightState::add_new_target_state_reached_callback(std::function &&s void LightState::set_default_transition_length(uint32_t default_transition_length) { this->default_transition_length_ = default_transition_length; } +uint32_t LightState::get_default_transition_length() const { return this->default_transition_length_; } +void LightState::set_flash_transition_length(uint32_t flash_transition_length) { + this->flash_transition_length_ = flash_transition_length; +} +uint32_t LightState::get_flash_transition_length() const { return this->flash_transition_length_; } void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; } void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } bool LightState::supports_effects() { return !this->effects_.empty(); } @@ -234,7 +239,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length) { // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works if (this->transformer_ != nullptr) - end_colors = this->transformer_->get_target_values(); + end_colors = this->transformer_->get_start_values(); this->transformer_ = make_unique(*this); this->transformer_->setup(end_colors, target, length); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index dfea9a15f4..dd42aa76db 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -99,6 +99,11 @@ class LightState : public Nameable, public Component { /// Set the default transition length, i.e. the transition length when no transition is provided. void set_default_transition_length(uint32_t default_transition_length); + uint32_t get_default_transition_length() const; + + /// Set the flash transition length + void set_flash_transition_length(uint32_t flash_transition_length); + uint32_t get_flash_transition_length() const; /// Set the gamma correction factor void set_gamma_correct(float gamma_correct); @@ -188,6 +193,8 @@ class LightState : public Nameable, public Component { /// Default transition length for all transitions in ms. uint32_t default_transition_length_{}; + /// Transition length to use for flash transitions. + uint32_t flash_transition_length_{}; /// Gamma correction factor for the light. float gamma_correct_{}; /// Restore mode of the light. diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index fd0bfd20f3..adcaebe0c8 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -58,7 +58,45 @@ class LightFlashTransformer : public LightTransformer { public: LightFlashTransformer(LightState &state) : state_(state) {} - optional apply() override { return this->get_target_values(); } + void start() override { + this->transition_length_ = this->state_.get_flash_transition_length(); + if (this->transition_length_ * 2 > this->length_) + this->transition_length_ = this->length_ / 2; + + // do not create transition if length is 0 + if (this->transition_length_ == 0) + return; + + // first transition to original target + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->target_values_, this->transition_length_); + } + + optional apply() override { + // transition transformer does not handle 0 length as progress returns nan + if (this->transition_length_ == 0) + return this->target_values_; + + if (this->transformer_ != nullptr) { + if (!this->transformer_->is_finished()) { + return this->transformer_->apply(); + } else { + this->transformer_->stop(); + this->transformer_ = nullptr; + } + } + + if (millis() > this->start_time_ + this->length_ - this->transition_length_ && + !this->secondary_transition_occurred_) { + // second transition back to start value + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); + this->secondary_transition_occurred_ = true; + } + + // once transition is complete, don't change states until next transition + return optional(); + } // Restore the original values after the flash. void stop() override { @@ -69,6 +107,9 @@ class LightFlashTransformer : public LightTransformer { protected: LightState &state_; + uint32_t transition_length_; + bool secondary_transition_occurred_{false}; + std::unique_ptr transformer_{nullptr}; }; } // namespace light diff --git a/esphome/components/power_supply/power_supply.cpp b/esphome/components/power_supply/power_supply.cpp index f50adac6f9..a492919202 100644 --- a/esphome/components/power_supply/power_supply.cpp +++ b/esphome/components/power_supply/power_supply.cpp @@ -42,7 +42,7 @@ void PowerSupply::request_high_power() { void PowerSupply::unrequest_high_power() { this->active_requests_--; if (this->active_requests_ < 0) { - // we're just going to use 0 as our now counter. + // we're just going to use 0 as our new counter. this->active_requests_ = 0; } diff --git a/esphome/const.py b/esphome/const.py index 579d8ef4c6..0ddc5da706 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -241,6 +241,7 @@ CONF_FILTERS = "filters" CONF_FINGER_ID = "finger_id" CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FLASH_LENGTH = "flash_length" +CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FOR = "for" CONF_FORCE_UPDATE = "force_update" From edcd88123d37e7484160331a21a1a4f0673d0a98 Mon Sep 17 00:00:00 2001 From: Petko Bordjukov Date: Thu, 2 Sep 2021 02:46:15 +0300 Subject: [PATCH 163/285] iBeacon support for ble_presence (#1627) --- .../components/ble_presence/binary_sensor.py | 36 ++++++++- .../ble_presence/ble_presence_device.h | 79 +++++++++++++++---- esphome/components/ble_rssi/sensor.py | 2 +- .../components/esp32_ble_tracker/__init__.py | 14 +++- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 ++ esphome/const.py | 3 + tests/test2.yaml | 5 ++ 7 files changed, 125 insertions(+), 22 deletions(-) diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index c58d29e6be..2a242c3aca 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_SERVICE_UUID, + CONF_IBEACON_MAJOR, + CONF_IBEACON_MINOR, + CONF_IBEACON_UUID, + CONF_ID, +) DEPENDENCIES = ["esp32_ble_tracker"] @@ -13,17 +20,30 @@ BLEPresenceDevice = ble_presence_ns.class_( esp32_ble_tracker.ESPBTDeviceListener, ) + +def _validate(config): + if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") + if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: + raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID") + return config + + CONFIG_SCHEMA = cv.All( binary_sensor.BINARY_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(BLEPresenceDevice), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, + cv.Optional(CONF_IBEACON_UUID): cv.uuid, } ) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA), - cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), + cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID), + _validate, ) @@ -50,5 +70,15 @@ async def to_code(config): ) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) + + if CONF_IBEACON_UUID in config: + ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) + cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) + + if CONF_IBEACON_MAJOR in config: + cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) + + if CONF_IBEACON_MINOR in config: + cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index bce6a9cf98..dfc36d68cb 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -14,41 +14,78 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, public Component { public: void set_address(uint64_t address) { - this->by_address_ = true; + this->match_by_ = MATCH_BY_MAC_ADDRESS; this->address_ = address; } void set_service_uuid16(uint16_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); } void set_service_uuid32(uint32_t uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); } void set_service_uuid128(uint8_t *uuid) { - this->by_address_ = false; + this->match_by_ = MATCH_BY_SERVICE_UUID; this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); } + void set_ibeacon_uuid(uint8_t *uuid) { + this->match_by_ = MATCH_BY_IBEACON_UUID; + this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); + } + void set_ibeacon_major(uint16_t major) { + this->check_ibeacon_major_ = true; + this->ibeacon_major_ = major; + } + void set_ibeacon_minor(uint16_t minor) { + this->check_ibeacon_minor_ = true; + this->ibeacon_minor_ = minor; + } void on_scan_end() override { if (!this->found_) this->publish_state(false); this->found_ = false; } bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - if (this->by_address_) { - if (device.address_uint64() == this->address_) { - this->publish_state(true); - this->found_ = true; - return true; - } - } else { - for (auto uuid : device.get_service_uuids()) { - if (this->uuid_ == uuid) { - this->publish_state(device.get_rssi()); + switch (this->match_by_) { + case MATCH_BY_MAC_ADDRESS: + if (device.address_uint64() == this->address_) { + this->publish_state(true); this->found_ = true; return true; } - } + break; + case MATCH_BY_SERVICE_UUID: + for (auto uuid : device.get_service_uuids()) { + if (this->uuid_ == uuid) { + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; + } + } + break; + case MATCH_BY_IBEACON_UUID: + if (!device.get_ibeacon().has_value()) { + return false; + } + + auto ibeacon = device.get_ibeacon().value(); + + if (this->ibeacon_uuid_ != ibeacon.get_uuid()) { + return false; + } + + if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) { + return false; + } + + if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) { + return false; + } + + this->publish_state(device.get_rssi()); + this->found_ = true; + return true; } return false; } @@ -56,10 +93,20 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, float get_setup_priority() const override { return setup_priority::DATA; } protected: + enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; + MATCH_TYPE match_by_; + bool found_{false}; - bool by_address_{false}; + uint64_t address_; + esp32_ble_tracker::ESPBTUUID uuid_; + + esp32_ble_tracker::ESPBTUUID ibeacon_uuid_; + uint16_t ibeacon_major_; + bool check_ibeacon_major_; + uint16_t ibeacon_minor_; + bool check_ibeacon_minor_; }; } // namespace ble_presence diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index bca73328f9..0c4308b11a 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -60,5 +60,5 @@ async def to_code(config): ) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 18f1c46ff2..fec0f6dcfb 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -108,6 +108,16 @@ def as_hex(value): def as_hex_array(value): + value = value.replace("-", "") + cpp_array = [ + f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] + ] + return cg.RawExpression( + "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(cpp_array)) + ) + + +def as_reversed_hex_array(value): value = value.replace("-", "") cpp_array = [ f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] @@ -193,7 +203,7 @@ async def to_code(config): elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid32_format): cg.add(trigger.set_service_uuid32(as_hex(conf[CONF_SERVICE_UUID]))) elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid128_format): - uuid128 = as_hex_array(conf[CONF_SERVICE_UUID]) + uuid128 = as_reversed_hex_array(conf[CONF_SERVICE_UUID]) cg.add(trigger.set_service_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) @@ -205,7 +215,7 @@ async def to_code(config): elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid32_format): cg.add(trigger.set_manufacturer_uuid32(as_hex(conf[CONF_MANUFACTURER_ID]))) elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid128_format): - uuid128 = as_hex_array(conf[CONF_MANUFACTURER_ID]) + uuid128 = as_reversed_hex_array(conf[CONF_MANUFACTURER_ID]) cg.add(trigger.set_manufacturer_uuid128(uuid128)) if CONF_MAC_ADDRESS in conf: cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index b3db651655..e1cd3975e8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -434,6 +434,14 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e } for (auto &data : this->manufacturer_datas_) { ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + if (this->get_ibeacon().has_value()) { + auto ibeacon = this->get_ibeacon().value(); + ESP_LOGVV(TAG, " iBeacon data:"); + ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str()); + ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major()); + ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor()); + ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power()); + } } for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); diff --git a/esphome/const.py b/esphome/const.py index 0ddc5da706..06ce736a85 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -279,6 +279,9 @@ CONF_HUMIDITY = "humidity" CONF_HYSTERESIS = "hysteresis" CONF_I2C = "i2c" CONF_I2C_ID = "i2c_id" +CONF_IBEACON_MAJOR = "ibeacon_major" +CONF_IBEACON_MINOR = "ibeacon_minor" +CONF_IBEACON_UUID = "ibeacon_uuid" CONF_ICON = "icon" CONF_ID = "id" CONF_IDENTITY = "identity" diff --git a/tests/test2.yaml b/tests/test2.yaml index 0db47965fe..54932953d5 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -294,6 +294,11 @@ binary_sensor: - platform: ble_presence service_uuid: '11223344-5566-7788-99aa-bbccddeeff00' name: 'BLE Test Service 128 Presence' + - platform: ble_presence + ibeacon_uuid: '11223344-5566-7788-99aa-bbccddeeff00' + ibeacon_major: 100 + ibeacon_minor: 1 + name: 'BLE Test iBeacon Presence' - platform: esp32_touch name: 'ESP32 Touch Pad GPIO27' pin: GPIO27 From 9937ad7fa03cbf999ed82976cb9c5b921f749f9f Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Wed, 1 Sep 2021 18:56:40 -0500 Subject: [PATCH 164/285] Cleanup flash transitions (#2227) --- esphome/components/light/transformers.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index adcaebe0c8..d501d53f72 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -86,12 +86,10 @@ class LightFlashTransformer : public LightTransformer { } } - if (millis() > this->start_time_ + this->length_ - this->transition_length_ && - !this->secondary_transition_occurred_) { + if (millis() > this->start_time_ + this->length_ - this->transition_length_) { // second transition back to start value this->transformer_ = this->state_.get_output()->create_default_transition(); this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); - this->secondary_transition_occurred_ = true; } // once transition is complete, don't change states until next transition @@ -108,7 +106,6 @@ class LightFlashTransformer : public LightTransformer { protected: LightState &state_; uint32_t transition_length_; - bool secondary_transition_occurred_{false}; std::unique_ptr transformer_{nullptr}; }; From a4d024f43d20b93e2ec69935f5703be1ca128446 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Wed, 1 Sep 2021 19:16:11 -0500 Subject: [PATCH 165/285] Add is_on and is_off conditions for the fan component (#2225) Co-authored-by: Chris Nussbaum --- esphome/components/fan/__init__.py | 26 ++++++++++++++++++++++++++ esphome/components/fan/automation.h | 17 +++++++++++++++++ tests/test1.yaml | 3 +++ 3 files changed, 46 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 27dee3271f..6bf0d1ca1a 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -42,6 +42,9 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) +FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) +FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) + FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(FanState), @@ -171,6 +174,29 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): return var +@automation.register_condition( + "fan.is_on", + FanIsOnCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + } + ), +) +@automation.register_condition( + "fan.is_off", + FanIsOffCondition, + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(FanState), + } + ), +) +async def fan_is_on_off_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren) + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_define("USE_FAN") diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 29a8e8d992..abcad82569 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -50,6 +50,23 @@ template class ToggleAction : public Action { FanState *state_; }; +template class FanIsOnCondition : public Condition { + public: + explicit FanIsOnCondition(FanState *state) : state_(state) {} + bool check(Ts... x) override { return this->state_->state; } + + protected: + FanState *state_; +}; +template class FanIsOffCondition : public Condition { + public: + explicit FanIsOffCondition(FanState *state) : state_(state) {} + bool check(Ts... x) override { return !this->state_->state; } + + protected: + FanState *state_; +}; + class FanTurnOnTrigger : public Trigger<> { public: FanTurnOnTrigger(FanState *state) { diff --git a/tests/test1.yaml b/tests/test1.yaml index a3f7a97281..f26e441192 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -129,6 +129,8 @@ mqtt: - mqtt.connected: - light.is_on: kitchen - light.is_off: kitchen + - fan.is_on: fan_speed + - fan.is_off: fan_speed then: - lambda: |- int data = x["my_data"]; @@ -1868,6 +1870,7 @@ fan: oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed + id: fan_speed output: pca_6 speed_count: 10 name: 'Living Room Fan 2' From 910f812737174f9c2c85097248c856f9deedcf8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Sep 2021 16:20:42 +1200 Subject: [PATCH 166/285] Bump pylint from 2.9.6 to 2.10.2 (#2197) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.9.6 to 2.10.2. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.9.6...v2.10.2) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b0de4b727b..59085b33e2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.9.6 +pylint==2.10.2 flake8==3.9.2 black==21.8b0 pexpect==4.8.0 From b01bc76dc5a48f24c575db4ae4b0019058399ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 3 Sep 2021 06:37:18 +0200 Subject: [PATCH 167/285] mqtt_sensor: properly send state_class via MQTT (#2228) --- esphome/components/mqtt/mqtt_sensor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index ce7e89c584..d440e30fc4 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,8 +61,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) - root["state_class"] = "measurement"; + if (this->sensor_->state_class != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->state_class); config.command_topic = false; } From 00aaf84c37a748ddf3744236c3c3100ad4206fab Mon Sep 17 00:00:00 2001 From: DAVe3283 Date: Thu, 2 Sep 2021 22:58:30 -0600 Subject: [PATCH 168/285] Fix uptime's state_class (esphome/issues#2337) (#2205) --- esphome/components/uptime/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 6ea3cca189..7989f3befc 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, ICON_TIMER, ) @@ -16,7 +16,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_SECOND, icon=ICON_TIMER, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { From f364788c035174debd5b3a627358910fbc1792a9 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 4 Sep 2021 04:32:33 +0200 Subject: [PATCH 169/285] Expose WHITE/CWWW/RGBCT color modes over MQTT (#2231) --- esphome/components/mqtt/mqtt_light.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index b702e6e425..f53be9c010 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -38,18 +38,23 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover root["color_mode"] = true; JsonArray &color_modes = root.createNestedArray("supported_color_modes"); - if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE)) + if (traits.supports_color_mode(ColorMode::ON_OFF)) + color_modes.add("onoff"); + if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) + color_modes.add("brightness"); + if (traits.supports_color_mode(ColorMode::WHITE)) + color_modes.add("white"); + if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || + traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) color_modes.add("color_temp"); if (traits.supports_color_mode(ColorMode::RGB)) color_modes.add("rgb"); - if (traits.supports_color_mode(ColorMode::RGB_WHITE)) + if (traits.supports_color_mode(ColorMode::RGB_WHITE) || + // HA doesn't support RGBCT, and there's no CWWW->CT emulation in ESPHome yet, so ignore CT control for now + traits.supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE)) color_modes.add("rgbw"); if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE)) color_modes.add("rgbww"); - if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) - color_modes.add("brightness"); - if (traits.supports_color_mode(ColorMode::ON_OFF)) - color_modes.add("onoff"); // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) From 54de0ca0da91c2abc8fb6fd4d5144bf0af811656 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sat, 4 Sep 2021 04:46:53 +0200 Subject: [PATCH 170/285] Reject template select/number/switches that don't handle user input (#2230) --- .../components/template/number/__init__.py | 11 +++-- .../components/template/select/__init__.py | 22 ++++++++-- .../components/template/switch/__init__.py | 43 ++++++++++++++----- tests/test1.yaml | 1 + 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 22bbaacc15..887f6b15ad 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -29,12 +29,16 @@ def validate_min_max(config): def validate(config): if CONF_LAMBDA in config: - if CONF_OPTIMISTIC in config: + if config[CONF_OPTIMISTIC]: raise cv.Invalid("optimistic cannot be used with lambda") if CONF_INITIAL_VALUE in config: raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set." + ) return config @@ -46,7 +50,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, cv.Optional(CONF_RESTORE_VALUE): cv.boolean, @@ -75,8 +79,7 @@ async def to_code(config): cg.add(var.set_template(template_)) else: - if CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_INITIAL_VALUE in config: cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) if CONF_RESTORE_VALUE in config: diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 3a707628a8..6562beb7f8 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -30,6 +30,21 @@ def validate_initial_value_in_options(config): return config +def validate(config): + if CONF_LAMBDA in config: + if config[CONF_OPTIMISTIC]: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_OPTION in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: + raise cv.Invalid( + "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set." + ) + return config + + CONFIG_SCHEMA = cv.All( select.SELECT_SCHEMA.extend( { @@ -38,13 +53,14 @@ CONFIG_SCHEMA = cv.All( cv.ensure_list(cv.string_strict), cv.Length(min=1) ), cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_OPTION): cv.string_strict, cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_initial_value_in_options, + validate, ) @@ -60,9 +76,7 @@ async def to_code(config): cg.add(var.set_template(template_)) else: - if CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION])) if CONF_RESTORE_VALUE in config: diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index b00710dfb7..6095a7c561 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -16,17 +16,38 @@ from .. import template_ns TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component) -CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateSwitch), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, - cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, - cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(single=True), - cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) + +def validate(config): + if ( + not config[CONF_OPTIMISTIC] + and CONF_TURN_ON_ACTION not in config + and CONF_TURN_OFF_ACTION not in config + ): + raise cv.Invalid( + "Either optimistic mode must be enabled, or turn_on_action or turn_off_action must be set, " + "to handle the switch being set." + ) + return config + + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateSwitch), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, + cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_RESTORE_STATE, default=False): cv.boolean, + } + ).extend(cv.COMPONENT_SCHEMA), + validate, +) async def to_code(config): diff --git a/tests/test1.yaml b/tests/test1.yaml index f26e441192..bfdf9c3bea 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1862,6 +1862,7 @@ switch: inverted: False - platform: template id: ble1_status + optimistic: true fan: - platform: binary From 77508f7e44c56b4e64291ead4cd33b79d92c4d52 Mon Sep 17 00:00:00 2001 From: Christian Ferbar <5595808+ferbar@users.noreply.github.com> Date: Sat, 4 Sep 2021 04:49:34 +0200 Subject: [PATCH 171/285] Fix UARTComponent hardware vs software UART0 conflict (#2229) Co-authored-by: Oxan van Leeuwen --- esphome/components/uart/uart.h | 5 +++++ esphome/components/uart/uart_esp8266.cpp | 28 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 1dc1e18412..8ac00658f4 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -118,6 +118,11 @@ class UARTComponent : public Component, public Stream { uint8_t stop_bits_; uint8_t data_bits_; UARTParityOptions parity_; + + private: +#ifdef ARDUINO_ARCH_ESP8266 + static bool serial0InUse; +#endif }; #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index c45f48644c..5cb625f2ff 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -4,11 +4,17 @@ #include "esphome/core/helpers.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" -# + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + namespace esphome { namespace uart { static const char *const TAG = "uart_esp8266"; +bool UARTComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + uint32_t UARTComponent::get_config() { uint32_t config = 0; @@ -49,15 +55,31 @@ void UARTComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { + if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(1) == 1 && + this->rx_pin_.value_or(3) == 3 +#ifdef USE_LOGGER + // we will use UART0 if logger isn't using it in swapped mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0_SWAP) +#endif + ) { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - } else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) { + UARTComponent::serial0InUse = true; + } else if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(15) == 15 && + this->rx_pin_.value_or(13) == 13 +#ifdef USE_LOGGER + // we will use UART0 swapped if logger isn't using it in regular mode + && (logger::global_logger->get_hw_serial() == nullptr || + logger::global_logger->get_uart() != logger::UART_SELECTION_UART0) +#endif + ) { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); + UARTComponent::serial0InUse = true; } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); From ca12b8aa5644cc551508650ce7e7c4bef34c7888 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:22:15 +1200 Subject: [PATCH 172/285] Move to use zeroconf library instead of inline copy (#2192) --- esphome/dashboard/dashboard.py | 4 +- esphome/helpers.py | 4 +- esphome/zeroconf.py | 772 +++------------------------------ 3 files changed, 53 insertions(+), 727 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 8016bf7bbd..a5e9766eea 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -41,7 +41,7 @@ from .util import password_hash # pylint: disable=unused-import, wrong-import-order from typing import Optional # noqa -from esphome.zeroconf import DashboardStatus, Zeroconf +from esphome.zeroconf import DashboardStatus, EsphomeZeroconf _LOGGER = logging.getLogger(__name__) @@ -501,7 +501,7 @@ def _ping_func(filename, address): class MDNSStatusThread(threading.Thread): def run(self): - zc = Zeroconf() + zc = EsphomeZeroconf() def on_update(dat): for key, b in dat.items(): diff --git a/esphome/helpers.py b/esphome/helpers.py index c766cc1ac7..a1cb4367c5 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -97,10 +97,10 @@ def is_ip_address(host): def _resolve_with_zeroconf(host): from esphome.core import EsphomeError - from esphome.zeroconf import Zeroconf + from esphome.zeroconf import EsphomeZeroconf try: - zc = Zeroconf() + zc = EsphomeZeroconf() except Exception as err: raise EsphomeError( "Cannot start mDNS sockets, is this a docker container without " diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index a44c7c9114..bc3f905261 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,520 +1,27 @@ -# Custom zeroconf implementation based on python-zeroconf -# (https://github.com/jstasiak/python-zeroconf) that supports Python 2 - -import errno -import logging -import select import socket -import struct -import sys import threading import time - -import ifaddr - -log = logging.getLogger(__name__) - -# Some timing constants - -_LISTENER_TIME = 200 - -# Some DNS constants - -_MDNS_ADDR = "224.0.0.251" -_MDNS_PORT = 5353 - -_MAX_MSG_ABSOLUTE = 8966 - -_FLAGS_QR_MASK = 0x8000 # query response mask -_FLAGS_QR_QUERY = 0x0000 # query -_FLAGS_QR_RESPONSE = 0x8000 # response - -_FLAGS_AA = 0x0400 # Authoritative answer -_FLAGS_TC = 0x0200 # Truncated -_FLAGS_RD = 0x0100 # Recursion desired -_FLAGS_RA = 0x8000 # Recursion available - -_FLAGS_Z = 0x0040 # Zero -_FLAGS_AD = 0x0020 # Authentic data -_FLAGS_CD = 0x0010 # Checking disabled - -_CLASS_IN = 1 -_CLASS_CS = 2 -_CLASS_CH = 3 -_CLASS_HS = 4 -_CLASS_NONE = 254 -_CLASS_ANY = 255 -_CLASS_MASK = 0x7FFF -_CLASS_UNIQUE = 0x8000 - -_TYPE_A = 1 -_TYPE_NS = 2 -_TYPE_MD = 3 -_TYPE_MF = 4 -_TYPE_CNAME = 5 -_TYPE_SOA = 6 -_TYPE_MB = 7 -_TYPE_MG = 8 -_TYPE_MR = 9 -_TYPE_NULL = 10 -_TYPE_WKS = 11 -_TYPE_PTR = 12 -_TYPE_HINFO = 13 -_TYPE_MINFO = 14 -_TYPE_MX = 15 -_TYPE_TXT = 16 -_TYPE_AAAA = 28 -_TYPE_SRV = 33 -_TYPE_ANY = 255 - -# Mapping constants to names -int2byte = struct.Struct(">B").pack - - -# Exceptions -class Error(Exception): - pass - - -class IncomingDecodeError(Error): - pass - - -# pylint: disable=no-init -class QuietLogger: - _seen_logs = {} - - @classmethod - def log_exception_warning(cls, logger_data=None): - exc_info = sys.exc_info() - exc_str = str(exc_info[1]) - if exc_str not in cls._seen_logs: - # log at warning level the first time this is seen - cls._seen_logs[exc_str] = exc_info - logger = log.warning - else: - logger = log.debug - if logger_data is not None: - logger(*logger_data) - logger("Exception occurred:", exc_info=True) - - @classmethod - def log_warning_once(cls, *args): - msg_str = args[0] - if msg_str not in cls._seen_logs: - cls._seen_logs[msg_str] = 0 - logger = log.warning - else: - logger = log.debug - cls._seen_logs[msg_str] += 1 - logger(*args) - - -class DNSEntry: - """A DNS entry""" - - def __init__(self, name, type_, class_): - self.key = name.lower() - self.name = name - self.type = type_ - self.class_ = class_ & _CLASS_MASK - self.unique = (class_ & _CLASS_UNIQUE) != 0 - - -class DNSQuestion(DNSEntry): - """A DNS question entry""" - - def __init__(self, name, type_, class_): - DNSEntry.__init__(self, name, type_, class_) - - def answered_by(self, rec): - """Returns true if the question is answered by the record""" - return ( - self.class_ == rec.class_ - and (self.type == rec.type or self.type == _TYPE_ANY) - and self.name == rec.name - ) - - -class DNSRecord(DNSEntry): - """A DNS record - like a DNS entry, but has a TTL""" - - def __init__(self, name, type_, class_, ttl): - DNSEntry.__init__(self, name, type_, class_) - self.ttl = 15 - self.created = time.time() - - def write(self, out): - """Abstract method""" - raise NotImplementedError - - def is_expired(self, now): - return self.created + self.ttl <= now - - def is_removable(self, now): - return self.created + self.ttl * 2 <= now - - -class DNSAddress(DNSRecord): - """A DNS address record""" - - def __init__(self, name, type_, class_, ttl, address): - DNSRecord.__init__(self, name, type_, class_, ttl) - self.address = address - - def write(self, out): - """Used in constructing an outgoing packet""" - out.write_string(self.address) - - -class DNSText(DNSRecord): - """A DNS text record""" - - def __init__(self, name, type_, class_, ttl, text): - assert isinstance(text, (bytes, type(None))) - DNSRecord.__init__(self, name, type_, class_, ttl) - self.text = text - - def write(self, out): - """Used in constructing an outgoing packet""" - out.write_string(self.text) - - -class DNSIncoming(QuietLogger): - """Object representation of an incoming DNS packet""" - - def __init__(self, data): - """Constructor from string holding bytes of packet""" - self.offset = 0 - self.data = data - self.questions = [] - self.answers = [] - self.id = 0 - self.flags = 0 # type: int - self.num_questions = 0 - self.num_answers = 0 - self.num_authorities = 0 - self.num_additionals = 0 - self.valid = False - - try: - self.read_header() - self.read_questions() - self.read_others() - self.valid = True - - except (IndexError, struct.error, IncomingDecodeError): - self.log_exception_warning( - ("Choked at offset %d while unpacking %r", self.offset, data) - ) - - def unpack(self, format_): - length = struct.calcsize(format_) - info = struct.unpack(format_, self.data[self.offset : self.offset + length]) - self.offset += length - return info - - def read_header(self): - """Reads header portion of packet""" - ( - self.id, - self.flags, - self.num_questions, - self.num_answers, - self.num_authorities, - self.num_additionals, - ) = self.unpack(b"!6H") - - def read_questions(self): - """Reads questions section of packet""" - for _ in range(self.num_questions): - name = self.read_name() - type_, class_ = self.unpack(b"!HH") - - question = DNSQuestion(name, type_, class_) - self.questions.append(question) - - def read_character_string(self): - """Reads a character string from the packet""" - length = self.data[self.offset] - self.offset += 1 - return self.read_string(length) - - def read_string(self, length): - """Reads a string of a given length from the packet""" - info = self.data[self.offset : self.offset + length] - self.offset += length - return info - - def read_unsigned_short(self): - """Reads an unsigned short from the packet""" - return self.unpack(b"!H")[0] - - def read_others(self): - """Reads the answers, authorities and additionals section of the - packet""" - n = self.num_answers + self.num_authorities + self.num_additionals - for _ in range(n): - domain = self.read_name() - type_, class_, ttl, length = self.unpack(b"!HHiH") - - rec = None - if type_ == _TYPE_A: - rec = DNSAddress(domain, type_, class_, ttl, self.read_string(4)) - elif type_ == _TYPE_TXT: - rec = DNSText(domain, type_, class_, ttl, self.read_string(length)) - elif type_ == _TYPE_AAAA: - rec = DNSAddress(domain, type_, class_, ttl, self.read_string(16)) - else: - # Try to ignore types we don't know about - # Skip the payload for the resource record so the next - # records can be parsed correctly - self.offset += length - - if rec is not None: - self.answers.append(rec) - - def is_query(self): - """Returns true if this is a query""" - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY - - def is_response(self): - """Returns true if this is a response""" - return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE - - def read_utf(self, offset, length): - """Reads a UTF-8 string of a given length from the packet""" - return str(self.data[offset : offset + length], "utf-8", "replace") - - def read_name(self): - """Reads a domain name from the packet""" - result = "" - off = self.offset - next_ = -1 - first = off - - while True: - length = self.data[off] - off += 1 - if length == 0: - break - t = length & 0xC0 - if t == 0x00: - result = "".join((result, self.read_utf(off, length) + ".")) - off += length - elif t == 0xC0: - if next_ < 0: - next_ = off + 1 - off = ((length & 0x3F) << 8) | self.data[off] - if off >= first: - raise IncomingDecodeError(f"Bad domain name (circular) at {off}") - first = off - else: - raise IncomingDecodeError(f"Bad domain name at {off}") - - if next_ >= 0: - self.offset = next_ - else: - self.offset = off - - return result - - -class DNSOutgoing: - """Object representation of an outgoing packet""" - - def __init__(self, flags): - self.finished = False - self.id = 0 - self.flags = flags - self.names = {} - self.data = [] - self.size = 12 - self.state = False - - self.questions = [] - self.answers = [] - - def add_question(self, record): - """Adds a question""" - self.questions.append(record) - - def pack(self, format_, value): - self.data.append(struct.pack(format_, value)) - self.size += struct.calcsize(format_) - - def write_byte(self, value): - """Writes a single byte to the packet""" - self.pack(b"!c", int2byte(value)) - - def insert_short(self, index, value): - """Inserts an unsigned short in a certain position in the packet""" - self.data.insert(index, struct.pack(b"!H", value)) - self.size += 2 - - def write_short(self, value): - """Writes an unsigned short to the packet""" - self.pack(b"!H", value) - - def write_int(self, value): - """Writes an unsigned integer to the packet""" - self.pack(b"!I", int(value)) - - def write_string(self, value): - """Writes a string to the packet""" - assert isinstance(value, bytes) - self.data.append(value) - self.size += len(value) - - def write_utf(self, s): - """Writes a UTF-8 string of a given length to the packet""" - utfstr = s.encode("utf-8") - length = len(utfstr) - self.write_byte(length) - self.write_string(utfstr) - - def write_character_string(self, value): - assert isinstance(value, bytes) - length = len(value) - self.write_byte(length) - self.write_string(value) - - def write_name(self, name): - # split name into each label - parts = name.split(".") - if not parts[-1]: - parts.pop() - - # construct each suffix - name_suffices = [".".join(parts[i:]) for i in range(len(parts))] - - # look for an existing name or suffix - for count, sub_name in enumerate(name_suffices): - if sub_name in self.names: - break - else: - count = len(name_suffices) - - # note the new names we are saving into the packet - name_length = len(name.encode("utf-8")) - for suffix in name_suffices[:count]: - self.names[suffix] = ( - self.size + name_length - len(suffix.encode("utf-8")) - 1 - ) - - # write the new names out. - for part in parts[:count]: - self.write_utf(part) - - # if we wrote part of the name, create a pointer to the rest - if count != len(name_suffices): - # Found substring in packet, create pointer - index = self.names[name_suffices[count]] - self.write_byte((index >> 8) | 0xC0) - self.write_byte(index & 0xFF) - else: - # this is the end of a name - self.write_byte(0) - - def write_question(self, question): - self.write_name(question.name) - self.write_short(question.type) - self.write_short(question.class_) - - def packet(self): - if not self.state: - for question in self.questions: - self.write_question(question) - self.state = True - - self.insert_short(0, 0) # num additionals - self.insert_short(0, 0) # num authorities - self.insert_short(0, 0) # num answers - self.insert_short(0, len(self.questions)) - self.insert_short(0, self.flags) # _FLAGS_QR_QUERY - self.insert_short(0, 0) - return b"".join(self.data) - - -class Engine(threading.Thread): - def __init__(self, zc): - threading.Thread.__init__(self, name="zeroconf-Engine") - self.daemon = True - self.zc = zc - self.readers = {} - self.timeout = 5 - self.condition = threading.Condition() - self.start() - - def run(self): - while not self.zc.done: - # pylint: disable=len-as-condition - with self.condition: - rs = self.readers.keys() - if len(rs) == 0: - # No sockets to manage, but we wait for the timeout - # or addition of a socket - self.condition.wait(self.timeout) - - if len(rs) != 0: - try: - rr, _, _ = select.select(rs, [], [], self.timeout) - if not self.zc.done: - for socket_ in rr: - reader = self.readers.get(socket_) - if reader: - reader.handle_read(socket_) - - except OSError as e: - # If the socket was closed by another thread, during - # shutdown, ignore it and exit - if e.args[0] != socket.EBADF or not self.zc.done: - raise - - def add_reader(self, reader, socket_): - with self.condition: - self.readers[socket_] = reader - self.condition.notify() - - def del_reader(self, socket_): - with self.condition: - del self.readers[socket_] - self.condition.notify() - - -class Listener(QuietLogger): - def __init__(self, zc): - self.zc = zc - self.data = None - - def handle_read(self, socket_): - try: - data, (addr, port) = socket_.recvfrom(_MAX_MSG_ABSOLUTE) - except Exception: # pylint: disable=broad-except - self.log_exception_warning() - return - - log.debug("Received from %r:%r: %r ", addr, port, data) - - self.data = data - msg = DNSIncoming(data) - if not msg.valid or msg.is_query(): - pass - else: - self.zc.handle_response(msg) - - -class RecordUpdateListener: - def update_record(self, zc, now, record): - raise NotImplementedError() +from typing import Optional + +from zeroconf import ( + _CLASS_IN, + _FLAGS_QR_QUERY, + _TYPE_A, + DNSAddress, + DNSOutgoing, + DNSRecord, + DNSQuestion, + RecordUpdateListener, + Zeroconf, +) class HostResolver(RecordUpdateListener): - def __init__(self, name): + def __init__(self, name: str): self.name = name - self.address = None + self.address: Optional[bytes] = None - def update_record(self, zc, now, record): + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: if record is None: return if record.type == _TYPE_A: @@ -522,14 +29,14 @@ class HostResolver(RecordUpdateListener): if record.name == self.name: self.address = record.address - def request(self, zc, timeout): + def request(self, zc: Zeroconf, timeout: float) -> bool: now = time.time() delay = 0.2 next_ = now + delay last = now + timeout try: - zc.add_listener(self) + zc.add_listener(self, None) while self.address is None: if last <= now: # Timeout @@ -550,56 +57,52 @@ class HostResolver(RecordUpdateListener): class DashboardStatus(RecordUpdateListener, threading.Thread): - def __init__(self, zc, on_update): + PING_AFTER = 15 * 1000 # Send new mDNS request after 15 seconds + OFFLINE_AFTER = PING_AFTER * 2 # Offline if no mDNS response after 30 seconds + + def __init__(self, zc: Zeroconf, on_update) -> None: threading.Thread.__init__(self) self.zc = zc - self.query_hosts = set() - self.key_to_host = {} - self.cache = {} + self.query_hosts: set[str] = set() + self.key_to_host: dict[str, str] = {} self.stop_event = threading.Event() self.query_event = threading.Event() self.on_update = on_update - def update_record(self, zc, now, record): - if record is None: - return - if record.type in (_TYPE_A, _TYPE_AAAA, _TYPE_TXT): - assert isinstance(record, DNSEntry) - if record.name in self.query_hosts: - self.cache.setdefault(record.name, []).insert(0, record) - self.purge_cache() + def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: + pass - def purge_cache(self): - new_cache = {} - for host, records in self.cache.items(): - if host not in self.query_hosts: - continue - new_records = [rec for rec in records if not rec.is_removable(time.time())] - if new_records: - new_cache[host] = new_records - self.cache = new_cache - self.on_update({key: self.host_status(key) for key in self.key_to_host}) - - def request_query(self, hosts): + def request_query(self, hosts: dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts self.query_event.set() - def stop(self): + def stop(self) -> None: self.stop_event.set() self.query_event.set() - def host_status(self, key): - return self.key_to_host.get(key) in self.cache + def host_status(self, key: str) -> bool: + entries = self.zc.cache.entries_with_name(key) + if not entries: + return False + now = time.time() * 1000 - def run(self): - self.zc.add_listener(self) + return any( + (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries + ) + + def run(self) -> None: + self.zc.add_listener(self, None) while not self.stop_event.is_set(): - self.purge_cache() + self.on_update( + {key: self.host_status(host) for key, host in self.key_to_host.items()} + ) + now = time.time() * 1000 for host in self.query_hosts: - if all( - record.is_expired(time.time()) - for record in self.cache.get(host, []) + entries = self.zc.cache.entries_with_name(host) + if not entries or all( + (entry.created + DashboardStatus.PING_AFTER) <= now + for entry in entries ): out = DNSOutgoing(_FLAGS_QR_QUERY) out.add_question(DNSQuestion(host, _TYPE_A, _CLASS_IN)) @@ -609,186 +112,9 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): self.zc.remove_listener(self) -def get_all_addresses(): - return list( - { - addr.ip - for iface in ifaddr.get_adapters() - for addr in iface.ips - if addr.is_IPv4 - and addr.network_prefix != 32 # Host only netmask 255.255.255.255 - } - ) - - -def new_socket(): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - # SO_REUSEADDR should be equivalent to SO_REUSEPORT for - # multicast UDP sockets (p 731, "TCP/IP Illustrated, - # Volume 2"), but some BSD-derived systems require - # SO_REUSEPORT to be specified explicitly. Also, not all - # versions of Python have SO_REUSEPORT available. - # Catch OSError and socket.error for kernel versions <3.9 because lacking - # SO_REUSEPORT support. - try: - reuseport = socket.SO_REUSEPORT - except AttributeError: - pass - else: - try: - s.setsockopt(socket.SOL_SOCKET, reuseport, 1) - except OSError as err: - # OSError on python 3, socket.error on python 2 - if err.errno != errno.ENOPROTOOPT: - raise - - # OpenBSD needs the ttl and loop values for the IP_MULTICAST_TTL and - # IP_MULTICAST_LOOP socket options as an unsigned char. - ttl = struct.pack(b"B", 255) - s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) - loop = struct.pack(b"B", 1) - s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, loop) - - s.bind(("", _MDNS_PORT)) - return s - - -class Zeroconf(QuietLogger): - def __init__(self): - # hook for threads - self._GLOBAL_DONE = False - - self._listen_socket = new_socket() - interfaces = get_all_addresses() - - self._respond_sockets = [] - - for i in interfaces: - try: - _value = socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(i) - self._listen_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, _value - ) - except OSError as e: - _errno = e.args[0] - if _errno == errno.EADDRINUSE: - log.info( - "Address in use when adding %s to multicast group, " - "it is expected to happen on some systems", - i, - ) - elif _errno == errno.EADDRNOTAVAIL: - log.info( - "Address not available when adding %s to multicast " - "group, it is expected to happen on some systems", - i, - ) - continue - elif _errno == errno.EINVAL: - log.info( - "Interface of %s does not support multicast, " - "it is expected in WSL", - i, - ) - continue - - else: - raise - - respond_socket = new_socket() - respond_socket.setsockopt( - socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(i) - ) - - self._respond_sockets.append(respond_socket) - - self.listeners = [] - - self.condition = threading.Condition() - - self.engine = Engine(self) - self.listener = Listener(self) - self.engine.add_reader(self.listener, self._listen_socket) - - @property - def done(self): - return self._GLOBAL_DONE - - def wait(self, timeout): - """Calling thread waits for a given number of milliseconds or - until notified.""" - with self.condition: - self.condition.wait(timeout) - - def notify_all(self): - """Notifies all waiting threads""" - with self.condition: - self.condition.notify_all() - - def resolve_host(self, host, timeout=3.0): +class EsphomeZeroconf(Zeroconf): + def resolve_host(self, host: str, timeout=3.0): info = HostResolver(host) if info.request(self, timeout): return socket.inet_ntoa(info.address) return None - - def add_listener(self, listener): - self.listeners.append(listener) - self.notify_all() - - def remove_listener(self, listener): - """Removes a listener.""" - try: - self.listeners.remove(listener) - self.notify_all() - except Exception as e: # pylint: disable=broad-except - log.exception("Unknown error, possibly benign: %r", e) - - def update_record(self, now, rec): - """Used to notify listeners of new information that has updated - a record.""" - for listener in self.listeners: - listener.update_record(self, now, rec) - self.notify_all() - - def handle_response(self, msg): - """Deal with incoming response packets. All answers - are held in the cache, and listeners are notified.""" - now = time.time() - for record in msg.answers: - self.update_record(now, record) - - def send(self, out): - """Sends an outgoing packet.""" - packet = out.packet() - log.debug("Sending %r (%d bytes) as %r...", out, len(packet), packet) - for s in self._respond_sockets: - if self._GLOBAL_DONE: - return - try: - bytes_sent = s.sendto(packet, 0, (_MDNS_ADDR, _MDNS_PORT)) - except Exception: # pylint: disable=broad-except - # on send errors, log the exception and keep going - self.log_exception_warning() - else: - if bytes_sent != len(packet): - self.log_warning_once( - "!!! sent %d out of %d bytes to %r" - % (bytes_sent, len(packet), s) - ) - - def close(self): - """Ends the background threads, and prevent this instance from - servicing further queries.""" - if not self._GLOBAL_DONE: - self._GLOBAL_DONE = True - # shutdown recv socket and thread - self.engine.del_reader(self._listen_socket) - self._listen_socket.close() - self.engine.join() - - # shutdown the rest - self.notify_all() - for s in self._respond_sockets: - s.close() From f9b0666adf760591444301d60662ea2d302d96e5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:23:06 +1200 Subject: [PATCH 173/285] Allow using a git source for a package (#2193) --- .../external_components/__init__.py | 91 ++----------- esphome/components/packages/__init__.py | 125 +++++++++++++++++- esphome/config.py | 4 + esphome/config_validation.py | 25 ++-- esphome/const.py | 3 + esphome/git.py | 74 +++++++++++ 6 files changed, 231 insertions(+), 91 deletions(-) create mode 100644 esphome/git.py diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 8f6e2bece4..110a8d95ed 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -1,13 +1,12 @@ import re import logging from pathlib import Path -import subprocess -import hashlib -import datetime import esphome.config_validation as cv from esphome.const import ( CONF_COMPONENTS, + CONF_REF, + CONF_REFRESH, CONF_SOURCE, CONF_URL, CONF_TYPE, @@ -15,7 +14,7 @@ from esphome.const import ( CONF_PATH, ) from esphome.core import CORE -from esphome import loader +from esphome import git, loader _LOGGER = logging.getLogger(__name__) @@ -23,19 +22,11 @@ DOMAIN = CONF_EXTERNAL_COMPONENTS TYPE_GIT = "git" TYPE_LOCAL = "local" -CONF_REFRESH = "refresh" -CONF_REF = "ref" - - -def validate_git_ref(value): - if re.match(r"[a-zA-Z0-9\-_.\./]+", value) is None: - raise cv.Invalid("Not a valid git ref") - return value GIT_SCHEMA = { cv.Required(CONF_URL): cv.url, - cv.Optional(CONF_REF): validate_git_ref, + cv.Optional(CONF_REF): cv.git_ref, } LOCAL_SCHEMA = { cv.Required(CONF_PATH): cv.directory, @@ -68,14 +59,6 @@ def validate_source_shorthand(value): return SOURCE_SCHEMA(conf) -def validate_refresh(value: str): - if value.lower() == "always": - return validate_refresh("0s") - if value.lower() == "never": - return validate_refresh("1000y") - return cv.positive_time_period_seconds(value) - - SOURCE_SCHEMA = cv.Any( validate_source_shorthand, cv.typed_schema( @@ -90,7 +73,7 @@ SOURCE_SCHEMA = cv.Any( CONFIG_SCHEMA = cv.ensure_list( { cv.Required(CONF_SOURCE): SOURCE_SCHEMA, - cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, validate_refresh), + cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), cv.Optional(CONF_COMPONENTS, default="all"): cv.Any( "all", cv.ensure_list(cv.string) ), @@ -102,65 +85,13 @@ async def to_code(config): pass -def _compute_destination_path(key: str) -> Path: - base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN - h = hashlib.new("sha256") - h.update(key.encode()) - return base_dir / h.hexdigest()[:8] - - -def _run_git_command(cmd, cwd=None): - try: - ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) - except FileNotFoundError as err: - raise cv.Invalid( - "git is not installed but required for external_components.\n" - "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" - ) from err - - if ret.returncode != 0 and ret.stderr: - err_str = ret.stderr.decode("utf-8") - lines = [x.strip() for x in err_str.splitlines()] - if lines[-1].startswith("fatal:"): - raise cv.Invalid(lines[-1][len("fatal: ") :]) - raise cv.Invalid(err_str) - - def _process_git_config(config: dict, refresh) -> str: - key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - _LOGGER.info("Cloning %s", key) - _LOGGER.debug("Location: %s", repo_dir) - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in config: - cmd += ["--branch", config[CONF_REF]] - cmd += ["--", config[CONF_URL], str(repo_dir)] - _run_git_command(cmd) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime - ) - if age.total_seconds() > refresh.total_seconds: - _LOGGER.info("Updating %s", key) - _LOGGER.debug("Location: %s", repo_dir) - # Stash local changes (if any) - _run_git_command( - ["git", "stash", "push", "--include-untracked"], str(repo_dir) - ) - # Fetch remote ref - cmd = ["git", "fetch", "--", "origin"] - if CONF_REF in config: - cmd.append(config[CONF_REF]) - _run_git_command(cmd, str(repo_dir)) - # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + repo_dir = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=refresh, + domain=DOMAIN, + ) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 8c5c9a0144..330ffc2bf2 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,6 +1,19 @@ +import re +from pathlib import Path +from esphome.core import EsphomeError + +from esphome import git, yaml_util +from esphome.const import ( + CONF_FILE, + CONF_FILES, + CONF_PACKAGES, + CONF_REF, + CONF_REFRESH, + CONF_URL, +) import esphome.config_validation as cv -from esphome.const import CONF_PACKAGES +DOMAIN = CONF_PACKAGES def _merge_package(full_old, full_new): @@ -23,11 +36,119 @@ def _merge_package(full_old, full_new): return merge(full_old, full_new) +def validate_git_package(config: dict): + new_config = config + for key, conf in config.items(): + if CONF_URL in conf: + try: + conf = BASE_SCHEMA(conf) + if CONF_FILE in conf: + new_config[key][CONF_FILES] = [conf[CONF_FILE]] + del new_config[key][CONF_FILE] + except cv.MultipleInvalid as e: + with cv.prepend_path([key]): + raise e + except cv.Invalid as e: + raise cv.Invalid( + "Extra keys not allowed in git based package", + path=[key] + e.path, + ) from e + return new_config + + +def validate_yaml_filename(value): + value = cv.string(value) + + if not (value.endswith(".yaml") or value.endswith(".yml")): + raise cv.Invalid("Only YAML (.yaml / .yml) files are supported.") + + return value + + +def validate_source_shorthand(value): + if not isinstance(value, str): + raise cv.Invalid("Shorthand only for strings") + + m = re.match( + r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)/([a-zA-Z0-9\-_.\./]+)(?:@([a-zA-Z0-9\-_.\./]+))?", + value, + ) + if m is None: + raise cv.Invalid( + "Source is not a file system path or in expected github://username/name/[sub-folder/]file-path.yml[@branch-or-tag] format!" + ) + + conf = { + CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", + CONF_FILE: m.group(3), + } + if m.group(4): + conf[CONF_REF] = m.group(4) + + # print(conf) + return BASE_SCHEMA(conf) + + +BASE_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_URL): cv.url, + cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, + cv.Exclusive(CONF_FILES, "files"): cv.All( + cv.ensure_list(validate_yaml_filename), + cv.Length(min=1), + ), + cv.Optional(CONF_REF): cv.git_ref, + cv.Optional(CONF_REFRESH, default="1d"): cv.All( + cv.string, cv.source_refresh + ), + } + ), + cv.has_at_least_one_key(CONF_FILE, CONF_FILES), +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + str: cv.Any(validate_source_shorthand, BASE_SCHEMA, dict), + } + ), + validate_git_package, +) + + +def _process_base_package(config: dict) -> dict: + repo_dir = git.clone_or_update( + url=config[CONF_URL], + ref=config.get(CONF_REF), + refresh=config[CONF_REFRESH], + domain=DOMAIN, + ) + files: str = config[CONF_FILES] + + packages = {} + for file in files: + yaml_file: Path = repo_dir / file + + if not yaml_file.is_file(): + raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES]) + + try: + packages[file] = yaml_util.load_yaml(yaml_file) + except EsphomeError as e: + raise cv.Invalid( + f"{file} is not a valid YAML file. Please check the file contents." + ) from e + return {"packages": packages} + + def do_packages_pass(config: dict): if CONF_PACKAGES not in config: return config packages = config[CONF_PACKAGES] with cv.prepend_path(CONF_PACKAGES): + packages = CONFIG_SCHEMA(packages) if not isinstance(packages, dict): raise cv.Invalid( "Packages must be a key to value mapping, got {} instead" @@ -37,6 +158,8 @@ def do_packages_pass(config: dict): for package_name, package_config in packages.items(): with cv.prepend_path(package_name): recursive_package = package_config + if CONF_URL in package_config: + package_config = _process_base_package(package_config) if isinstance(package_config, dict): recursive_package = do_packages_pass(package_config) config = _merge_package(recursive_package, config) diff --git a/esphome/config.py b/esphome/config.py index 93413a009c..de261f7eba 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -333,6 +333,8 @@ def validate_config(config, command_line_substitutions): result.add_error(err) return result + CORE.raw_config = config + # 1. Load substitutions if CONF_SUBSTITUTIONS in config: from esphome.components import substitutions @@ -348,6 +350,8 @@ def validate_config(config, command_line_substitutions): result.add_error(err) return result + CORE.raw_config = config + # 1.1. Check for REPLACEME special value try: recursive_check_replaceme(config) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 4df65a38e3..fb659c41ea 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -33,7 +33,6 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, - CONF_PACKAGES, ) from esphome.core import ( CORE, @@ -1455,15 +1454,7 @@ class OnlyWith(Optional): @property def default(self): # pylint: disable=unsupported-membership-test - if self._component in CORE.raw_config or ( - CONF_PACKAGES in CORE.raw_config - and self._component - in [ - k - for package in CORE.raw_config[CONF_PACKAGES].values() - for k in package.keys() - ] - ): + if self._component in CORE.raw_config: return self._default return vol.UNDEFINED @@ -1633,3 +1624,17 @@ def url(value): if not parsed.scheme or not parsed.netloc: raise Invalid("Expected a URL scheme and host") return parsed.geturl() + + +def git_ref(value): + if re.match(r"[a-zA-Z0-9\-_.\./]+", value) is None: + raise Invalid("Not a valid git ref") + return value + + +def source_refresh(value: str): + if value.lower() == "always": + return source_refresh("0s") + if value.lower() == "never": + return source_refresh("1000y") + return positive_time_period_seconds(value) diff --git a/esphome/const.py b/esphome/const.py index 06ce736a85..7132cb1d1d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -235,6 +235,7 @@ CONF_FAN_WITH_COOLING = "fan_with_cooling" CONF_FAN_WITH_HEATING = "fan_with_heating" CONF_FAST_CONNECT = "fast_connect" CONF_FILE = "file" +CONF_FILES = "files" CONF_FILTER = "filter" CONF_FILTER_OUT = "filter_out" CONF_FILTERS = "filters" @@ -525,8 +526,10 @@ CONF_REACTIVE_POWER = "reactive_power" CONF_REBOOT_TIMEOUT = "reboot_timeout" CONF_RECEIVE_TIMEOUT = "receive_timeout" CONF_RED = "red" +CONF_REF = "ref" CONF_REFERENCE_RESISTANCE = "reference_resistance" CONF_REFERENCE_TEMPERATURE = "reference_temperature" +CONF_REFRESH = "refresh" CONF_REPEAT = "repeat" CONF_REPOSITORY = "repository" CONF_RESET_PIN = "reset_pin" diff --git a/esphome/git.py b/esphome/git.py new file mode 100644 index 0000000000..12c6b41648 --- /dev/null +++ b/esphome/git.py @@ -0,0 +1,74 @@ +from pathlib import Path +import subprocess +import hashlib +import logging + +from datetime import datetime + +from esphome.core import CORE, TimePeriodSeconds +import esphome.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + + +def run_git_command(cmd, cwd=None): + try: + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + + if ret.returncode != 0 and ret.stderr: + err_str = ret.stderr.decode("utf-8") + lines = [x.strip() for x in err_str.splitlines()] + if lines[-1].startswith("fatal:"): + raise cv.Invalid(lines[-1][len("fatal: ") :]) + raise cv.Invalid(err_str) + + +def _compute_destination_path(key: str, domain: str) -> Path: + base_dir = Path(CORE.config_dir) / ".esphome" / domain + h = hashlib.new("sha256") + h.update(key.encode()) + return base_dir / h.hexdigest()[:8] + + +def clone_or_update( + *, url: str, ref: str = None, refresh: TimePeriodSeconds, domain: str +) -> Path: + key = f"{url}@{ref}" + repo_dir = _compute_destination_path(key, domain) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if ref is not None: + cmd += ["--branch", ref] + cmd += ["--", url, str(repo_dir)] + run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime) + if age.total_seconds() > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if ref is not None: + cmd.append(ref) + run_git_command(cmd, str(repo_dir)) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + + return repo_dir From ff6bed54c66a01942c8881aeb849521959c91f0e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:30:47 +1200 Subject: [PATCH 174/285] Remove last_reset_type and convert all those sensors to TOTAL_INCREASING (#2233) --- esphome/components/api/api.proto | 3 +- esphome/components/api/api_connection.cpp | 1 - esphome/components/api/api_pb2.cpp | 8 +-- esphome/components/api/api_pb2.h | 2 +- esphome/components/atm90e32/sensor.py | 8 +-- esphome/components/demo/__init__.py | 6 +- esphome/components/demo/demo_sensor.h | 4 +- esphome/components/dsmr/sensor.py | 58 +++---------------- esphome/components/havells_solar/sensor.py | 13 ++--- esphome/components/hlw8012/sensor.py | 5 +- esphome/components/pulse_counter/sensor.py | 4 +- esphome/components/pulse_meter/sensor.py | 5 +- esphome/components/pzem004t/sensor.py | 5 +- esphome/components/pzemac/sensor.py | 5 +- esphome/components/sdm_meter/sensor.py | 14 ++--- esphome/components/selec_meter/sensor.py | 23 +++----- esphome/components/sensor/__init__.py | 28 +-------- esphome/components/sensor/sensor.cpp | 13 ----- esphome/components/sensor/sensor.h | 24 -------- .../components/total_daily_energy/sensor.py | 6 +- esphome/const.py | 8 --- script/api_protobuf/api_protobuf.py | 8 +-- 22 files changed, 60 insertions(+), 191 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index e3ef2d7c9e..7648ffeaa2 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -473,7 +473,8 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; - SensorLastResetType last_reset_type = 11; + // Last reset type removed in 2021.9.0 + SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; } message SensorStateResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2bf3af5f65..99e611be10 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -418,7 +418,6 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); - msg.last_reset_type = static_cast(sensor->last_reset_type); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index f5860bee64..d6b85d257c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1817,7 +1817,7 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va return true; } case 11: { - this->last_reset_type = value.as_enum(); + this->legacy_last_reset_type = value.as_enum(); return true; } case 12: { @@ -1879,7 +1879,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); - buffer.encode_enum(11, this->last_reset_type); + buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); } #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1928,8 +1928,8 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->state_class)); out.append("\n"); - out.append(" last_reset_type: "); - out.append(proto_enum_to_string(this->last_reset_type)); + out.append(" legacy_last_reset_type: "); + out.append(proto_enum_to_string(this->legacy_last_reset_type)); out.append("\n"); out.append(" disabled_by_default: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 93bfcd9b55..1371ab5248 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -510,7 +510,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; - enums::SensorLastResetType last_reset_type{}; + enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 28b49604ff..05e5250d89 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_LIGHTBULB, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -94,15 +94,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index b3ea47d869..fae8a2b07d 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -19,7 +19,6 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INVERTED, - CONF_LAST_RESET_TYPE, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_NAME, @@ -40,8 +39,8 @@ from esphome.const import ( ICON_BLUR, ICON_EMPTY, ICON_THERMOMETER, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, UNIT_EMPTY, UNIT_PERCENT, @@ -336,8 +335,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_UNIT_OF_MEASUREMENT: UNIT_WATT_HOURS, CONF_ACCURACY_DECIMALS: 0, CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, - CONF_LAST_RESET_TYPE: LAST_RESET_TYPE_AUTO, + CONF_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, }, ], ): [ diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 117468793b..344aaf26f8 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,8 +11,8 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool is_auto = this->last_reset_type == sensor::LAST_RESET_TYPE_AUTO; - if (is_auto) { + bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); } else { diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 84b263c2d5..2c05651d67 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -9,9 +9,9 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, - LAST_RESET_TYPE_NEVER, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_EMPTY, UNIT_VOLT, @@ -26,52 +26,22 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - "kWh", - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("total_imported_energy"): sensor.sensor_schema( "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE @@ -176,20 +146,10 @@ CONFIG_SCHEMA = cv.Schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE ), cv.Optional("gas_delivered"): sensor.sensor_schema( - "m³", - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - "m³", - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_MEASUREMENT, - LAST_RESET_TYPE_NEVER, + "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 1d685b9b2e..3ec12d5b83 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -13,9 +13,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, @@ -143,25 +142,23 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_HOURS, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_TODAY_GENERATION_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 75590f8572..11e9c8e4d4 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -18,8 +18,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -78,8 +78,7 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=1, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index 767728fc80..c7b89d41b0 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -13,7 +13,7 @@ from esphome.const import ( CONF_TOTAL, ICON_PULSE, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, + STATE_CLASS_TOTAL_INCREASING, UNIT_PULSES_PER_MINUTE, UNIT_PULSES, ) @@ -95,7 +95,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 18da842bad..454cb3a69d 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, ) @@ -64,8 +64,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( unit_of_measurement=UNIT_PULSES, icon=ICON_PULSE, accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index 23502e849a..70dec82c3f 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -50,8 +50,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 1616bf0ace..b6697e3d19 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -15,8 +15,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_ENERGY, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -55,8 +55,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_WATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( unit_of_measurement=UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index ffb88af5ac..8a0d9674a7 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -23,8 +23,8 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_FLASH, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_DEGREES, UNIT_HERTZ, @@ -104,29 +104,25 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ) diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 2d05d00380..168d3a3db2 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -20,8 +20,8 @@ from esphome.const import ( DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_HERTZ, UNIT_VOLT, @@ -54,50 +54,43 @@ SENSORS = { unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_ACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_ACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_APPARENT_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_HOURS, accuracy_decimals=2, device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_ACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_WATT, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 1bb4e25a17..bf0dbf62c4 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,7 +17,6 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, - CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -31,9 +30,6 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, - LAST_RESET_TYPE_AUTO, - LAST_RESET_TYPE_NEVER, - LAST_RESET_TYPE_NONE, DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_CARBON_MONOXIDE, @@ -85,15 +81,6 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") -LastResetTypes = sensor_ns.enum("LastResetType") -LAST_RESET_TYPES = { - LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, - LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, - LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, -} -validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") - - IS_PLATFORM_COMPONENT = True @@ -183,7 +170,9 @@ SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, - cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, + cv.Optional("last_reset_type"): cv.invalid( + "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." + ), cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -220,7 +209,6 @@ def sensor_schema( accuracy_decimals: int = _UNDEF, device_class: str = _UNDEF, state_class: str = _UNDEF, - last_reset_type: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement is not _UNDEF: @@ -253,14 +241,6 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} ) - if last_reset_type is not _UNDEF: - schema = schema.extend( - { - cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type - ): validate_last_reset_type - } - ) return schema @@ -511,8 +491,6 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) - if CONF_LAST_RESET_TYPE in config: - cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1a5c76db51..1dbc1c901a 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,18 +18,6 @@ const char *state_class_to_string(StateClass state_class) { } } -const char *last_reset_type_to_string(LastResetType last_reset_type) { - switch (last_reset_type) { - case LAST_RESET_TYPE_NEVER: - return "never"; - case LAST_RESET_TYPE_AUTO: - return "auto"; - case LAST_RESET_TYPE_NONE: - default: - return ""; - } -} - void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -80,7 +68,6 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } -void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index f0d7ba4887..34b8b26a54 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,10 +14,6 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ - if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ - (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ - ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ - } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -42,20 +38,6 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); -/** - * Sensor last reset types - */ -enum LastResetType : uint8_t { - /// This sensor does not support resetting. ie, it is not accumulative - LAST_RESET_TYPE_NONE = 0, - /// This sensor is expected to never reset its value - LAST_RESET_TYPE_NEVER = 1, - /// This sensor may reset and Home Assistant will watch for this - LAST_RESET_TYPE_AUTO = 2, -}; - -const char *last_reset_type_to_string(LastResetType last_reset_type); - /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -174,12 +156,6 @@ class Sensor : public Nameable { */ virtual std::string device_class(); - // The Last reset type of this sensor - LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; - - /// Manually set the Home Assistant last reset type for this sensor. - void set_last_reset_type(LastResetType last_reset_type); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index f1449432a0..46eaac98eb 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -5,9 +5,8 @@ from esphome.const import ( CONF_ID, CONF_TIME_ID, DEVICE_CLASS_ENERGY, - LAST_RESET_TYPE_AUTO, - STATE_CLASS_MEASUREMENT, CONF_METHOD, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["time"] @@ -28,8 +27,7 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_MEASUREMENT, - last_reset_type=LAST_RESET_TYPE_AUTO, + state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { diff --git a/esphome/const.py b/esphome/const.py index 7132cb1d1d..5c5037ceb2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -323,7 +323,6 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" -CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -857,10 +856,3 @@ STATE_CLASS_MEASUREMENT = "measurement" # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" - -# This sensor does not support resetting. ie, it is not accumulative -LAST_RESET_TYPE_NONE = "" -# This sensor is expected to never reset its value -LAST_RESET_TYPE_NEVER = "never" -# This sensor may reset and Home Assistant will watch for this -LAST_RESET_TYPE_AUTO = "auto" diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 6983090fd9..7ccdc5a24e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -778,9 +778,9 @@ def build_service_message_type(mt): hout += f"bool {func}(const {mt.name} &msg);\n" cout += f"bool {class_name}::{func}(const {mt.name} &msg) {{\n" if log: - cout += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' + cout += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - cout += f'#endif\n' + cout += f"#endif\n" # cout += f' this->set_nodelay({str(nodelay).lower()});\n' cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n" cout += f"}}\n" @@ -794,9 +794,9 @@ def build_service_message_type(mt): case += f"{mt.name} msg;\n" case += f"msg.decode(msg_data, msg_size);\n" if log: - case += f'#ifdef HAS_PROTO_MESSAGE_DUMP\n' + case += f"#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' - case += f'#endif\n' + case += f"#endif\n" case += f"this->{func}(msg);\n" if ifdef is not None: case += f"#endif\n" From 97eba1eecc7d1ee1aac8f3904315375dfd442b51 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:36:55 +1200 Subject: [PATCH 175/285] Dont dump legacy fields (#2241) --- esphome/components/api/api_pb2.cpp | 41 ----------------------------- script/api_protobuf/api_protobuf.py | 2 ++ 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d6b85d257c..dab0b29127 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -793,10 +793,6 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_state: "); - out.append(proto_enum_to_string(this->legacy_state)); - out.append("\n"); - out.append(" position: "); sprintf(buffer, "%g", this->position); out.append(buffer); @@ -880,10 +876,6 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_command)); out.append("\n"); - out.append(" legacy_command: "); - out.append(proto_enum_to_string(this->legacy_command)); - out.append("\n"); - out.append(" has_position: "); out.append(YESNO(this->has_position)); out.append("\n"); @@ -1330,22 +1322,6 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); } - out.append(" legacy_supports_brightness: "); - out.append(YESNO(this->legacy_supports_brightness)); - out.append("\n"); - - out.append(" legacy_supports_rgb: "); - out.append(YESNO(this->legacy_supports_rgb)); - out.append("\n"); - - out.append(" legacy_supports_white_value: "); - out.append(YESNO(this->legacy_supports_white_value)); - out.append("\n"); - - out.append(" legacy_supports_color_temperature: "); - out.append(YESNO(this->legacy_supports_color_temperature)); - out.append("\n"); - out.append(" min_mireds: "); sprintf(buffer, "%g", this->min_mireds); out.append(buffer); @@ -2760,11 +2736,6 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append(YESNO(this->bool_)); out.append("\n"); - out.append(" legacy_int: "); - sprintf(buffer, "%d", this->legacy_int); - out.append(buffer); - out.append("\n"); - out.append(" float_: "); sprintf(buffer, "%g", this->float_); out.append(buffer); @@ -3180,10 +3151,6 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_supports_away: "); - out.append(YESNO(this->legacy_supports_away)); - out.append("\n"); - out.append(" supports_action: "); out.append(YESNO(this->supports_action)); out.append("\n"); @@ -3342,10 +3309,6 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); - out.append("\n"); - out.append(" action: "); out.append(proto_enum_to_string(this->action)); out.append("\n"); @@ -3545,10 +3508,6 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); - out.append("\n"); - out.append(" has_fan_mode: "); out.append(YESNO(this->has_fan_mode)); out.append("\n"); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ccdc5a24e..7b9e7941ae 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -183,6 +183,8 @@ class TypeInfo: @property def dump_content(self): + if self.name.startswith("legacy_"): + return None o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" o += f'out.append("\\n");\n' From 2a653642f50c580d59e3fc7b87c6eca4400b9537 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:57:37 +1200 Subject: [PATCH 176/285] Fix encoding bug (#2242) --- esphome/dashboard/dashboard.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index a5e9766eea..ab9dd39735 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -608,9 +608,7 @@ class EditRequestHandler(BaseHandler): @bind_config def post(self, configuration=None): # pylint: disable=no-value-for-parameter - with open( - file=settings.rel_path(configuration), mode="wb", encoding="utf-8" - ) as f: + with open(file=settings.rel_path(configuration), mode="wb") as f: f.write(self.request.body) self.set_status(200) From e2d97b6f36d1a06e5e9620a855240b34a2b508af Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Sep 2021 08:57:58 +1200 Subject: [PATCH 177/285] Light: include ON_OFF capability to BRIGHTNESS ColorMode (#2204) --- esphome/components/light/color_mode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 0f5b7b4b93..77c377d39e 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -52,7 +52,7 @@ enum class ColorMode : uint8_t { /// Only on/off control. ON_OFF = (uint8_t) ColorCapability::ON_OFF, /// Dimmable light. - BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS, + BRIGHTNESS = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), /// White output only (use only if the light also has another color mode such as RGB). WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE), /// Controllable color temperature output. From 7f76f3726fe3cb5ea403b52d2fc190feaa2326a4 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Mon, 6 Sep 2021 04:47:13 +0200 Subject: [PATCH 178/285] LOG_UPDATE_INTERVAL: correctly report "never" (#2240) --- esphome/core/component.h | 6 +++++- esphome/core/scheduler.cpp | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/core/component.h b/esphome/core/component.h index b9a22c240e..a84f612dd9 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -38,8 +38,12 @@ extern const float LATE; } // namespace setup_priority +static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; + #define LOG_UPDATE_INTERVAL(this) \ - if (this->get_update_interval() < 100) { \ + if (this->get_update_interval() == SCHEDULER_DONT_RUN) { \ + ESP_LOGCONFIG(TAG, " Update Interval: never"); \ + } else if (this->get_update_interval() < 100) { \ ESP_LOGCONFIG(TAG, " Update Interval: %.3fs", this->get_update_interval() / 1000.0f); \ } else { \ ESP_LOGCONFIG(TAG, " Update Interval: %.1fs", this->get_update_interval() / 1000.0f); \ diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 60e0d4e9bd..5718e3b396 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -7,7 +7,6 @@ namespace esphome { static const char *const TAG = "scheduler"; -static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10; // Uncomment to debug scheduler From 4a6f1f150a837e654c7b6af8f26dec64c6bd5dc1 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Sun, 5 Sep 2021 21:48:28 -0500 Subject: [PATCH 179/285] Fix runtime exception due to dict typing (#2243) --- esphome/zeroconf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index bc3f905261..e94b59d3ae 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -1,7 +1,7 @@ import socket import threading import time -from typing import Optional +from typing import Dict, Optional from zeroconf import ( _CLASS_IN, @@ -64,7 +64,7 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): threading.Thread.__init__(self) self.zc = zc self.query_hosts: set[str] = set() - self.key_to_host: dict[str, str] = {} + self.key_to_host: Dict[str, str] = {} self.stop_event = threading.Event() self.query_event = threading.Event() self.on_update = on_update @@ -72,7 +72,7 @@ class DashboardStatus(RecordUpdateListener, threading.Thread): def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: pass - def request_query(self, hosts: dict[str, str]) -> None: + def request_query(self, hosts: Dict[str, str]) -> None: self.query_hosts = set(hosts.values()) self.key_to_host = hosts self.query_event.set() From 2d91e6b97760974ae6e5d3a02018b83a67ffd3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 6 Sep 2021 22:00:08 +0200 Subject: [PATCH 180/285] template: select: fix initial_value cannot be used with lambda (#2244) --- .../components/template/select/__init__.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/esphome/components/template/select/__init__.py b/esphome/components/template/select/__init__.py index 6562beb7f8..4eba77119d 100644 --- a/esphome/components/template/select/__init__.py +++ b/esphome/components/template/select/__init__.py @@ -19,17 +19,6 @@ TemplateSelect = template_ns.class_( CONF_SET_ACTION = "set_action" -def validate_initial_value_in_options(config): - if CONF_INITIAL_OPTION in config: - if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: - raise cv.Invalid( - f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]" - ) - else: - config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] - return config - - def validate(config): if CONF_LAMBDA in config: if config[CONF_OPTIMISTIC]: @@ -38,6 +27,14 @@ def validate(config): raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + elif CONF_INITIAL_OPTION in config: + if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: + raise cv.Invalid( + f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]" + ) + else: + config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: raise cv.Invalid( "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set." @@ -59,7 +56,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), - validate_initial_value_in_options, validate, ) From d9cb64b893701695d48c95e6114d83dacfa9bb7c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 7 Sep 2021 00:12:26 +0200 Subject: [PATCH 181/285] Add device classes new in HA 2021.9 (#2248) --- esphome/components/ccs811/sensor.py | 4 ++++ esphome/components/hm3301/sensor.py | 8 +++++++ esphome/components/mhz19/sensor.py | 2 ++ esphome/components/pm1006/sensor.py | 2 ++ esphome/components/pmsa003i/sensor.py | 9 ++++--- esphome/components/pmsx003/sensor.py | 9 ++++--- esphome/components/sds011/sensor.py | 4 ++++ esphome/components/senseair/sensor.py | 2 ++ esphome/components/sensor/__init__.py | 34 +++++++++++++++++++++------ esphome/components/sgp30/sensor.py | 4 ++++ esphome/components/sgp40/sensor.py | 2 ++ esphome/components/sm300d2/sensor.py | 8 +++++++ esphome/components/sps30/sensor.py | 6 +++++ esphome/components/zyaura/sensor.py | 2 ++ esphome/const.py | 18 ++++++++++---- 15 files changed, 97 insertions(+), 17 deletions(-) diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index 4c09a14c3e..c177ed6b5c 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -4,6 +4,8 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, ICON_RADIATOR, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -30,12 +32,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index fe1c6008d4..7cd81fec1d 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -6,6 +6,10 @@ from esphome.const import ( CONF_PM_2_5, CONF_PM_10_0, CONF_PM_1_0, + DEVICE_CLASS_AQI, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -45,24 +49,28 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_AQI): sensor.sensor_schema( unit_of_measurement=UNIT_INDEX, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, ).extend( { diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 1a111f7891..0081f42952 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_ID, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_CARBON_DIOXIDE, ICON_MOLECULE_CO2, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, @@ -34,6 +35,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 8ea0e303f3..0423be61e2 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_ID, CONF_PM_2_5, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_BLUR, @@ -23,6 +24,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_BLUR, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/pmsa003i/sensor.py b/esphome/components/pmsa003i/sensor.py index ac26270cfc..a9586d98cd 100644 --- a/esphome/components/pmsa003i/sensor.py +++ b/esphome/components/pmsa003i/sensor.py @@ -13,6 +13,9 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, ICON_COUNTER, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_EMPTY, ) @@ -39,19 +42,19 @@ CONFIG_SCHEMA = ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 2, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM10, ), cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index c3dd7d5a97..8b9e5c9af2 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -19,6 +19,9 @@ from esphome.const import ( CONF_PM_10_0UM, CONF_TEMPERATURE, CONF_TYPE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -75,19 +78,19 @@ CONFIG_SCHEMA = ( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 0, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_PM10, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, diff --git a/esphome/components/sds011/sensor.py b/esphome/components/sds011/sensor.py index 0997b47ef6..456d47ee91 100644 --- a/esphome/components/sds011/sensor.py +++ b/esphome/components/sds011/sensor.py @@ -7,6 +7,8 @@ from esphome.const import ( CONF_PM_2_5, CONF_RX_ONLY, CONF_UPDATE_INTERVAL, + DEVICE_CLASS_PM25, + DEVICE_CLASS_PM10, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, @@ -41,12 +43,14 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=1, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=1, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, diff --git a/esphome/components/senseair/sensor.py b/esphome/components/senseair/sensor.py index 739a8ada50..d423793873 100644 --- a/esphome/components/senseair/sensor.py +++ b/esphome/components/senseair/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_CO2, CONF_ID, ICON_MOLECULE_CO2, + DEVICE_CLASS_CARBON_DIOXIDE, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, ) @@ -41,6 +42,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index bf0dbf62c4..fd278be51e 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -31,21 +31,31 @@ from esphome.const import ( CONF_MQTT_ID, CONF_FORCE_UPDATE, DEVICE_CLASS_EMPTY, + DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, - DEVICE_CLASS_SIGNAL_STRENGTH, - DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority @@ -54,21 +64,31 @@ from esphome.util import Registry CODEOWNERS = ["@esphome/core"] DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, + DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, - DEVICE_CLASS_SIGNAL_STRENGTH, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_NITROGEN_DIOXIDE, + DEVICE_CLASS_NITROGEN_MONOXIDE, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_OZONE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SULPHUR_DIOXIDE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLTAGE, ] diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 3e33af3b4a..2596e0065d 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -7,6 +7,8 @@ from esphome.const import ( CONF_ECO2, CONF_TVOC, ICON_RADIATOR, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -34,12 +36,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 0f562048ac..7b96f867af 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -4,6 +4,7 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, ICON_RADIATOR, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, ) @@ -26,6 +27,7 @@ CONFIG_SCHEMA = ( sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ) .extend( diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py index 73cada0eb3..8452ee81f2 100644 --- a/esphome/components/sm300d2/sensor.py +++ b/esphome/components/sm300d2/sensor.py @@ -10,6 +10,10 @@ from esphome.const import ( CONF_PM_10_0, CONF_TEMPERATURE, CONF_HUMIDITY, + DEVICE_CLASS_CARBON_DIOXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_PM25, + DEVICE_CLASS_PM10, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, @@ -36,6 +40,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema( @@ -48,18 +53,21 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_GRAIN, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_GRAIN, accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/components/sps30/sensor.py b/esphome/components/sps30/sensor.py index 959b427861..27264cf942 100644 --- a/esphome/components/sps30/sensor.py +++ b/esphome/components/sps30/sensor.py @@ -13,6 +13,9 @@ from esphome.const import ( CONF_PMC_4_0, CONF_PMC_10_0, CONF_PM_SIZE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_METER, @@ -35,12 +38,14 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_4_0): sensor.sensor_schema( @@ -53,6 +58,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, icon=ICON_CHEMICAL_WEAPON, accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index f2273afa9e..74f1f9ec61 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_CO2, CONF_TEMPERATURE, CONF_HUMIDITY, + DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -35,6 +36,7 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/const.py b/esphome/const.py index 5c5037ceb2..6342e2a848 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -811,7 +811,6 @@ DEVICE_CLASS_COLD = "cold" DEVICE_CLASS_CONNECTIVITY = "connectivity" DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_GARAGE_DOOR = "garage_door" -DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_HEAT = "heat" DEVICE_CLASS_LIGHT = "light" DEVICE_CLASS_LOCK = "lock" @@ -832,20 +831,31 @@ DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_BATTERY = "battery" +DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_POWER = "power" # device classes of sensor component -DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" +DEVICE_CLASS_AQI = "aqi" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" +DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_CURRENT = "current" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" DEVICE_CLASS_MONETARY = "monetary" -DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" -DEVICE_CLASS_TEMPERATURE = "temperature" +DEVICE_CLASS_NITROGEN_DIOXIDE = "nitrogen_dioxide" +DEVICE_CLASS_NITROGEN_MONOXIDE = "nitrogen_monoxide" +DEVICE_CLASS_NITROUS_OXIDE = "nitrous_oxide" +DEVICE_CLASS_OZONE = "ozone" +DEVICE_CLASS_PM1 = "pm1" +DEVICE_CLASS_PM10 = "pm10" +DEVICE_CLASS_PM25 = "pm25" DEVICE_CLASS_POWER_FACTOR = "power_factor" DEVICE_CLASS_PRESSURE = "pressure" +DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" +DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide" +DEVICE_CLASS_TEMPERATURE = "temperature" DEVICE_CLASS_TIMESTAMP = "timestamp" +DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds" DEVICE_CLASS_VOLTAGE = "voltage" # state classes From dba502c75611a0f43fcd6a778f239e93c1ff3625 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 7 Sep 2021 22:57:20 +0200 Subject: [PATCH 182/285] Logger prevent recursive logging (#2251) --- esphome/components/logger/logger.cpp | 8 ++++++-- esphome/components/logger/logger.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index ce82a51b94..9d79037087 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -43,21 +43,24 @@ void Logger::write_header_(int level, const char *tag, int line) { } void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT - if (level > this->level_for(tag)) + if (level > this->level_for(tag) || recursion_guard_) return; + recursion_guard_ = true; this->reset_buffer_(); this->write_header_(level, tag, line); this->vprintf_to_buffer_(format, args); this->write_footer_(); this->log_message_(level, tag); + recursion_guard_ = false; } #ifdef USE_STORE_LOG_STR_IN_FLASH void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args) { // NOLINT - if (level > this->level_for(tag)) + if (level > this->level_for(tag) || recursion_guard_) return; + recursion_guard_ = true; this->reset_buffer_(); // copy format string const char *format_pgm_p = (PGM_P) format; @@ -78,6 +81,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr this->vprintf_to_buffer_(this->tx_buffer_, args); this->write_footer_(); this->log_message_(level, tag, offset); + recursion_guard_ = false; } #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 1724875229..365261cb91 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -113,6 +113,8 @@ class Logger : public Component { }; std::vector log_levels_; CallbackManager log_callback_{}; + /// Prevents recursive log calls, if true a log message is already being processed. + bool recursion_guard_ = false; }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) From b0533db2eb80143a40c650d52edeec7a9123ceb7 Mon Sep 17 00:00:00 2001 From: dgtal1 <27864579+dgtal1@users.noreply.github.com> Date: Wed, 8 Sep 2021 05:15:57 +0200 Subject: [PATCH 183/285] Add new trigger to fan component `on_speed_set` (#2246) --- esphome/components/fan/__init__.py | 10 ++++++++++ esphome/components/fan/automation.h | 18 ++++++++++++++++++ esphome/const.py | 1 + tests/test1.yaml | 3 +++ 4 files changed, 32 insertions(+) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 6bf0d1ca1a..46ff0c2d53 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME, + CONF_ON_SPEED_SET, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, @@ -41,6 +42,7 @@ ToggleAction = fan_ns.class_("ToggleAction", automation.Action) FanTurnOnTrigger = fan_ns.class_("FanTurnOnTrigger", automation.Trigger.template()) FanTurnOffTrigger = fan_ns.class_("FanTurnOffTrigger", automation.Trigger.template()) +FanSpeedSetTrigger = fan_ns.class_("FanSpeedSetTrigger", automation.Trigger.template()) FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) @@ -71,6 +73,11 @@ FAN_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), } ), + cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), + } + ), } ) @@ -110,6 +117,9 @@ async def setup_fan_core_(var, config): for conf in config.get(CONF_ON_TURN_OFF, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_SPEED_SET, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) async def register_fan(var, config): diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index abcad82569..7ff7c720df 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -103,5 +103,23 @@ class FanTurnOffTrigger : public Trigger<> { bool last_on_; }; +class FanSpeedSetTrigger : public Trigger<> { + public: + FanSpeedSetTrigger(FanState *state) { + state->add_on_state_callback([this, state]() { + auto speed = state->speed; + auto should_trigger = speed != !this->last_speed_; + this->last_speed_ = speed; + if (should_trigger) { + this->trigger(); + } + }); + this->last_speed_ = state->speed; + } + + protected: + int last_speed_; +}; + } // namespace fan } // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 6342e2a848..aff03b244b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -425,6 +425,7 @@ CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" CONF_ON_RELEASE = "on_release" CONF_ON_SHUTDOWN = "on_shutdown" +CONF_ON_SPEED_SET = "on_speed_set" CONF_ON_STATE = "on_state" CONF_ON_TAG = "on_tag" CONF_ON_TAG_REMOVED = "on_tag_removed" diff --git a/tests/test1.yaml b/tests/test1.yaml index bfdf9c3bea..da3289843d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1881,6 +1881,9 @@ fan: oscillation_command_topic: oscillation/command/topic speed_state_topic: speed/state/topic speed_command_topic: speed/command/topic + on_speed_set: + then: + - logger.log: "Fan speed was changed!" interval: - interval: 10s From 1be106c0b5f9dfa3b6df668a36975d3f7a7421ab Mon Sep 17 00:00:00 2001 From: wifwucite <74489218+wifwucite@users.noreply.github.com> Date: Wed, 8 Sep 2021 05:30:17 +0200 Subject: [PATCH 184/285] Fix fan speed restore issue on boot (#1867) --- esphome/components/binary/fan/binary_fan.cpp | 5 ++++- esphome/components/fan/fan_state.cpp | 2 +- esphome/components/speed/fan/speed_fan.cpp | 5 ++++- esphome/components/tuya/fan/tuya_fan.cpp | 4 ++++ esphome/components/tuya/fan/tuya_fan.h | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index eaf41829bb..2201fe576e 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -55,7 +55,10 @@ void BinaryFan::loop() { ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); } } -float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } + +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace binary } // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 9b4ae53937..a4883c5e2c 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -39,7 +39,7 @@ void FanState::setup() { call.set_direction(recovered.direction); call.perform(); } -float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } +float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } uint32_t FanState::hash_base() { return 418001110UL; } void FanStateCall::perform() const { diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 8c6ec54d4c..cb10db4ed4 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -56,7 +56,10 @@ void SpeedFan::loop() { ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); } } -float SpeedFan::get_setup_priority() const { return setup_priority::DATA; } + +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float SpeedFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace speed } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index e9f8ce8e96..f060b18eba 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -84,5 +84,9 @@ void TuyaFan::write_state() { } } +// We need a higher priority than the FanState component to make sure that the traits are set +// when that component sets itself up. +float TuyaFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } + } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index a24e7a218e..e96770d8c3 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -11,6 +11,7 @@ class TuyaFan : public Component { public: TuyaFan(Tuya *parent, fan::FanState *fan, int speed_count) : parent_(parent), fan_(fan), speed_count_(speed_count) {} void setup() override; + float get_setup_priority() const override; void dump_config() override; void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } From 6180ee8065db319a475692a1dbcbe02b5132e9a3 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Tue, 7 Sep 2021 22:36:49 -0500 Subject: [PATCH 185/285] Template sensors always publish on update interval (#2224) Co-authored-by: Chris Nussbaum --- .../components/template/sensor/template_sensor.cpp | 13 +++++++------ .../template/text_sensor/template_text_sensor.cpp | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 9324cb5dea..63cbd70db0 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -7,12 +7,13 @@ namespace template_ { static const char *const TAG = "template.sensor"; void TemplateSensor::update() { - if (!this->f_.has_value()) - return; - - auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); + if (this->f_.has_value()) { + auto val = (*this->f_)(); + if (val.has_value()) { + this->publish_state(*val); + } + } else if (!isnan(this->get_raw_state())) { + this->publish_state(this->get_raw_state()); } } float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/template/text_sensor/template_text_sensor.cpp b/esphome/components/template/text_sensor/template_text_sensor.cpp index 885ad47bbf..83bebb5bcf 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.cpp +++ b/esphome/components/template/text_sensor/template_text_sensor.cpp @@ -7,12 +7,13 @@ namespace template_ { static const char *const TAG = "template.text_sensor"; void TemplateTextSensor::update() { - if (!this->f_.has_value()) - return; - - auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); + if (this->f_.has_value()) { + auto val = (*this->f_)(); + if (val.has_value()) { + this->publish_state(*val); + } + } else if (this->has_state()) { + this->publish_state(this->state); } } float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } From f924e80f431a26f1c49c8eedd203b2f12317e4d0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 05:41:42 +0200 Subject: [PATCH 186/285] Socket component (#2250) --- CODEOWNERS | 1 + esphome/components/socket/__init__.py | 28 ++ .../components/socket/bsd_sockets_impl.cpp | 105 ++++ esphome/components/socket/headers.h | 117 +++++ .../components/socket/lwip_raw_tcp_impl.cpp | 475 ++++++++++++++++++ esphome/components/socket/socket.h | 42 ++ esphome/core/defines.h | 6 + script/ci-custom.py | 7 +- 8 files changed, 779 insertions(+), 2 deletions(-) create mode 100644 esphome/components/socket/__init__.py create mode 100644 esphome/components/socket/bsd_sockets_impl.cpp create mode 100644 esphome/components/socket/headers.h create mode 100644 esphome/components/socket/lwip_raw_tcp_impl.cpp create mode 100644 esphome/components/socket/socket.h diff --git a/CODEOWNERS b/CODEOWNERS index 40bf27aa43..ad670ede14 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -119,6 +119,7 @@ esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 +esphome/components/socket/* @esphome/core esphome/components/spi/* @esphome/core esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_spi/* @kbx81 diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py new file mode 100644 index 0000000000..8e9502be6d --- /dev/null +++ b/esphome/components/socket/__init__.py @@ -0,0 +1,28 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +CODEOWNERS = ["@esphome/core"] + +CONF_IMPLEMENTATION = "implementation" +IMPLEMENTATION_LWIP_TCP = "lwip_tcp" +IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets" + +CONFIG_SCHEMA = cv.Schema( + { + cv.SplitDefault( + CONF_IMPLEMENTATION, + esp8266=IMPLEMENTATION_LWIP_TCP, + esp32=IMPLEMENTATION_BSD_SOCKETS, + ): cv.one_of( + IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" + ), + } +) + + +async def to_code(config): + impl = config[CONF_IMPLEMENTATION] + if impl == IMPLEMENTATION_LWIP_TCP: + cg.add_define("USE_SOCKET_IMPL_LWIP_TCP") + elif impl == IMPLEMENTATION_BSD_SOCKETS: + cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp new file mode 100644 index 0000000000..a0cdb6ec42 --- /dev/null +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -0,0 +1,105 @@ +#include "socket.h" +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + +#include + +namespace esphome { +namespace socket { + +std::string format_sockaddr(const struct sockaddr_storage &storage) { + if (storage.ss_family == AF_INET) { + const struct sockaddr_in *addr = reinterpret_cast(&storage); + char buf[INET_ADDRSTRLEN]; + const char *ret = inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); + if (ret == NULL) + return {}; + return std::string{buf}; + } else if (storage.ss_family == AF_INET6) { + const struct sockaddr_in6 *addr = reinterpret_cast(&storage); + char buf[INET6_ADDRSTRLEN]; + const char *ret = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); + if (ret == NULL) + return {}; + return std::string{buf}; + } + return {}; +} + +class BSDSocketImpl : public Socket { + public: + BSDSocketImpl(int fd) : Socket(), fd_(fd) {} + ~BSDSocketImpl() override { + if (!closed_) { + close(); + } + } + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = ::accept(fd_, addr, addrlen); + if (fd == -1) + return {}; + return std::unique_ptr{new BSDSocketImpl(fd)}; + } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } + int close() override { + int ret = ::close(fd_); + closed_ = true; + return ret; + } + int shutdown(int how) override { return ::shutdown(fd_, how); } + + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(fd_, addr, addrlen); } + std::string getpeername() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getpeername((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(fd_, addr, addrlen); } + std::string getsockname() override { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + int err = this->getsockname((struct sockaddr *) &storage, &len); + if (err != 0) + return {}; + return format_sockaddr(storage); + } + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + return ::getsockopt(fd_, level, optname, optval, optlen); + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + return ::setsockopt(fd_, level, optname, optval, optlen); + } + int listen(int backlog) override { return ::listen(fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + int setblocking(bool blocking) override { + int fl = ::fcntl(fd_, F_GETFL, 0); + if (blocking) { + fl &= ~O_NONBLOCK; + } else { + fl |= O_NONBLOCK; + } + ::fcntl(fd_, F_SETFL, fl); + return 0; + } + + protected: + int fd_; + bool closed_ = false; +}; + +std::unique_ptr socket(int domain, int type, int protocol) { + int ret = ::socket(domain, type, protocol); + if (ret == -1) + return nullptr; + return std::unique_ptr{new BSDSocketImpl(ret)}; +} + +} // namespace socket +} // namespace esphome + +#endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h new file mode 100644 index 0000000000..a084823bdf --- /dev/null +++ b/esphome/components/socket/headers.h @@ -0,0 +1,117 @@ +#pragma once +#include "esphome/core/defines.h" + +// Helper file to include all socket-related system headers (or use our own +// definitions where system ones don't exist) + +#ifdef USE_SOCKET_IMPL_LWIP_TCP + +#define LWIP_INTERNAL +#include +#include "lwip/inet.h" +#include +#include + +/* Address families. */ +#define AF_UNSPEC 0 +#define AF_INET 2 +#define AF_INET6 10 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define IPPROTO_IP 0 +#define IPPROTO_TCP 6 +#define IPPROTO_IPV6 41 +#define IPPROTO_ICMPV6 58 + +#define TCP_NODELAY 0x01 + +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 1 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +/* Socket protocol types (TCP/UDP/RAW) */ +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define SOCK_RAW 3 + +#define SO_REUSEADDR 0x0004 /* Allow local address reuse */ +#define SO_KEEPALIVE 0x0008 /* keep connections alive */ +#define SO_BROADCAST 0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */ + +#define SOL_SOCKET 0xfff /* options for socket level */ + +typedef uint8_t sa_family_t; +typedef uint16_t in_port_t; + +struct sockaddr_in { + uint8_t sin_len; + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; +#define SIN_ZERO_LEN 8 + char sin_zero[SIN_ZERO_LEN]; +}; + +struct sockaddr_in6 { + uint8_t sin6_len; /* length of this structure */ + sa_family_t sin6_family; /* AF_INET6 */ + in_port_t sin6_port; /* Transport layer port # */ + uint32_t sin6_flowinfo; /* IPv6 flow information */ + struct in6_addr sin6_addr; /* IPv6 address */ + uint32_t sin6_scope_id; /* Set of interfaces for scope */ +}; + +struct sockaddr { + uint8_t sa_len; + sa_family_t sa_family; + char sa_data[14]; +}; + +struct sockaddr_storage { + uint8_t s2_len; + sa_family_t ss_family; + char s2_data1[2]; + uint32_t s2_data2[3]; + uint32_t s2_data3[3]; +}; +typedef uint32_t socklen_t; + +#ifdef ARDUINO_ARCH_ESP8266 +// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define +#ifdef INADDR_ANY +#undef INADDR_ANY +#endif +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif + +#define INADDR_ANY ((uint32_t) 0x00000000UL) +#endif + +#endif // USE_SOCKET_IMPL_LWIP_TCP + +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + +#include +#include +#include +#include +#include +#include + +#ifdef ARDUINO_ARCH_ESP32 +// arduino-esp32 declares a global var called INADDR_NONE which is replaced +// by the define +#ifdef INADDR_NONE +#undef INADDR_NONE +#endif +// not defined for ESP32 +typedef uint32_t socklen_t; +#endif // ARDUINO_ARCH_ESP32 + +#endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp new file mode 100644 index 0000000000..a5afea072e --- /dev/null +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -0,0 +1,475 @@ +#include "socket.h" +#include "esphome/core/defines.h" + +#ifdef USE_SOCKET_IMPL_LWIP_TCP + +#include "lwip/ip.h" +#include "lwip/netif.h" +#include "lwip/opt.h" +#include "lwip/tcp.h" +#include +#include +#include + +#include "esphome/core/log.h" + +namespace esphome { +namespace socket { + +static const char *const TAG = "lwip"; + +class LWIPRawImpl : public Socket { + public: + LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} + ~LWIPRawImpl() override { + if (pcb_ != nullptr) { + tcp_abort(pcb_); + pcb_ = nullptr; + } + } + + void init() { + tcp_arg(pcb_, this); + tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); + tcp_recv(pcb_, LWIPRawImpl::s_recv_fn); + tcp_err(pcb_, LWIPRawImpl::s_err_fn); + } + + std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return nullptr; + } + if (accepted_sockets_.empty()) { + errno = EWOULDBLOCK; + return nullptr; + } + std::unique_ptr sock = std::move(accepted_sockets_.front()); + accepted_sockets_.pop(); + if (addr != nullptr) { + sock->getpeername(addr, addrlen); + } + sock->init(); + return std::unique_ptr(std::move(sock)); + } + int bind(const struct sockaddr *name, socklen_t addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr) { + errno = EINVAL; + return 0; + } + ip_addr_t ip; + in_port_t port; + auto family = name->sa_family; +#if LWIP_IPV6 + if (family == AF_INET) { + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return -1; + } + auto *addr4 = reinterpret_cast(name); + port = ntohs(addr4->sin_port); + ip.type = IPADDR_TYPE_V4; + ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; + + } else if (family == AF_INET6) { + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return -1; + } + auto *addr6 = reinterpret_cast(name); + port = ntohs(addr6->sin6_port); + ip.type = IPADDR_TYPE_V6; + memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); + } else { + errno = EINVAL; + return -1; + } +#else + if (family != AF_INET) { + errno = EINVAL; + return -1; + } + auto *addr4 = reinterpret_cast(name); + port = ntohs(addr4->sin_port); + ip.addr = addr4->sin_addr.s_addr; +#endif + err_t err = tcp_bind(pcb_, &ip, port); + if (err == ERR_USE) { + errno = EADDRINUSE; + return -1; + } + if (err == ERR_VAL) { + errno = EINVAL; + return -1; + } + if (err != ERR_OK) { + errno = EIO; + return -1; + } + return 0; + } + int close() override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + err_t err = tcp_close(pcb_); + if (err != ERR_OK) { + tcp_abort(pcb_); + pcb_ = nullptr; + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + pcb_ = nullptr; + return 0; + } + int shutdown(int how) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + bool shut_rx = false, shut_tx = false; + if (how == SHUT_RD) { + shut_rx = true; + } else if (how == SHUT_WR) { + shut_tx = true; + } else if (how == SHUT_RDWR) { + shut_rx = shut_tx = true; + } else { + errno = EINVAL; + return -1; + } + err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); + if (err != ERR_OK) { + errno = err == ERR_MEM ? ENOMEM : EIO; + return -1; + } + return 0; + } + + int getpeername(struct sockaddr *name, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = pcb_->remote_port; + addr->sin_addr.s_addr = pcb_->remote_ip.addr; + return 0; + } + std::string getpeername() override { + if (pcb_ == nullptr) { + errno = EBADF; + return ""; + } + char buffer[24]; + uint32_t ip4 = pcb_->remote_ip.addr; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, + (ip4 >> 0) & 0xFF); + return std::string(buffer); + } + int getsockname(struct sockaddr *name, socklen_t *addrlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (name == nullptr || addrlen == nullptr) { + errno = EINVAL; + return -1; + } + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = pcb_->local_port; + addr->sin_addr.s_addr = pcb_->local_ip.addr; + return 0; + } + std::string getsockname() override { + if (pcb_ == nullptr) { + errno = EBADF; + return ""; + } + char buffer[24]; + uint32_t ip4 = pcb_->local_ip.addr; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, + (ip4 >> 0) & 0xFF); + return std::string(buffer); + } + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (optlen == nullptr || optval == nullptr) { + errno = EINVAL; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (*optlen < 4) { + errno = EINVAL; + return -1; + } + + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + *reinterpret_cast(optval) = 1; + *optlen = 4; + return 0; + } + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (*optlen < 4) { + errno = EINVAL; + return -1; + } + *reinterpret_cast(optval) = tcp_nagle_disabled(pcb_); + *optlen = 4; + return 0; + } + + errno = EINVAL; + return -1; + } + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (level == SOL_SOCKET && optname == SO_REUSEADDR) { + if (optlen != 4) { + errno = EINVAL; + return -1; + } + + // lwip doesn't seem to have this feature. Don't send an error + // to prevent warnings + return 0; + } + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + if (optlen != 4) { + errno = EINVAL; + return -1; + } + int val = *reinterpret_cast(optval); + if (val != 0) { + tcp_nagle_disable(pcb_); + } else { + tcp_nagle_enable(pcb_); + } + return 0; + } + + errno = EINVAL; + return -1; + } + int listen(int backlog) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog); + if (listen_pcb == nullptr) { + tcp_abort(pcb_); + pcb_ = nullptr; + errno = EOPNOTSUPP; + return -1; + } + // tcp_listen reallocates the pcb, replace ours + pcb_ = listen_pcb; + // set callbacks on new pcb + tcp_arg(pcb_, this); + tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); + return 0; + } + ssize_t read(void *buf, size_t len) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (rx_closed_ && rx_buf_ == nullptr) { + errno = ECONNRESET; + return -1; + } + if (len == 0) { + return 0; + } + if (rx_buf_ == nullptr) { + errno = EWOULDBLOCK; + return -1; + } + + size_t read = 0; + uint8_t *buf8 = reinterpret_cast(buf); + while (len && rx_buf_ != nullptr) { + size_t pb_len = rx_buf_->len; + size_t pb_left = pb_len - rx_buf_offset_; + if (pb_left == 0) + break; + size_t copysize = std::min(len, pb_left); + memcpy(buf8, reinterpret_cast(rx_buf_->payload) + rx_buf_offset_, copysize); + + if (pb_left == copysize) { + // full pb copied, free it + if (rx_buf_->next == nullptr) { + // last buffer in chain + pbuf_free(rx_buf_); + rx_buf_ = nullptr; + rx_buf_offset_ = 0; + } else { + auto *old_buf = rx_buf_; + rx_buf_ = rx_buf_->next; + pbuf_ref(rx_buf_); + pbuf_free(old_buf); + rx_buf_offset_ = 0; + } + } else { + rx_buf_offset_ += copysize; + } + tcp_recved(pcb_, copysize); + + buf8 += copysize; + len -= copysize; + read += copysize; + } + + return read; + } + ssize_t write(const void *buf, size_t len) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (len == 0) + return 0; + if (buf == nullptr) { + errno = EINVAL; + return 0; + } + auto space = tcp_sndbuf(pcb_); + if (space == 0) { + errno = EWOULDBLOCK; + return -1; + } + size_t to_send = std::min((size_t) space, len); + err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); + if (err == ERR_MEM) { + errno = EWOULDBLOCK; + return -1; + } + if (err != ERR_OK) { + errno = EIO; + return -1; + } + err = tcp_output(pcb_); + if (err != ERR_OK) { + errno = EIO; + return -1; + } + return to_send; + } + int setblocking(bool blocking) override { + if (pcb_ == nullptr) { + errno = EBADF; + return -1; + } + if (blocking) { + // blocking operation not supported + errno = EINVAL; + return -1; + } + return 0; + } + + err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + if (err != ERR_OK || newpcb == nullptr) { + // "An error code if there has been an error accepting. Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" + // https://www.nongnu.org/lwip/2_1_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d + // nothing to do here, we just don't push it to the queue + return ERR_OK; + } + accepted_sockets_.emplace(new LWIPRawImpl(newpcb)); + return ERR_OK; + } + void err_fn(err_t err) { + // "If a connection is aborted because of an error, the application is alerted of this event by + // the err callback." + // pcb is already freed when this callback is called + // ERR_RST: connection was reset by remote host + // ERR_ABRT: aborted through tcp_abort or TCP timer + pcb_ = nullptr; + } + err_t recv_fn(struct pbuf *pb, err_t err) { + if (err != 0) { + // "An error code if there has been an error receiving Only return ERR_ABRT if you have + // called tcp_abort from within the callback function!" + rx_closed_ = true; + return ERR_OK; + } + if (pb == nullptr) { + rx_closed_ = true; + return ERR_OK; + } + if (rx_buf_ == nullptr) { + // no need to copy because lwIP gave control of it to us + rx_buf_ = pb; + rx_buf_offset_ = 0; + } else { + pbuf_cat(rx_buf_, pb); + } + return ERR_OK; + } + + static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->accept_fn(newpcb, err); + } + + static void s_err_fn(void *arg, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->err_fn(err); + } + + static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { + LWIPRawImpl *arg_this = reinterpret_cast(arg); + return arg_this->recv_fn(pb, err); + } + + protected: + struct tcp_pcb *pcb_; + std::queue> accepted_sockets_; + bool rx_closed_ = false; + pbuf *rx_buf_ = nullptr; + size_t rx_buf_offset_ = 0; +}; + +std::unique_ptr socket(int domain, int type, int protocol) { + auto *pcb = tcp_new(); + if (pcb == nullptr) + return nullptr; + auto *sock = new LWIPRawImpl(pcb); + sock->init(); + return std::unique_ptr{sock}; +} + +} // namespace socket +} // namespace esphome + +#endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h new file mode 100644 index 0000000000..7a5ce79161 --- /dev/null +++ b/esphome/components/socket/socket.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +#include "headers.h" +#include "esphome/core/optional.h" + +namespace esphome { +namespace socket { + +class Socket { + public: + Socket() = default; + virtual ~Socket() = default; + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + + virtual std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual int bind(const struct sockaddr *addr, socklen_t addrlen) = 0; + virtual int close() = 0; + // not supported yet: + // virtual int connect(const std::string &address) = 0; + // virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0; + virtual int shutdown(int how) = 0; + + virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual std::string getpeername() = 0; + virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; + virtual std::string getsockname() = 0; + virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; + virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; + virtual int listen(int backlog) = 0; + virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t write(const void *buf, size_t len) = 0; + virtual int setblocking(bool blocking) = 0; + virtual int loop() { return 0; }; +}; + +std::unique_ptr socket(int domain, int type, int protocol); + +} // namespace socket +} // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ec10e86586..d73f7e9d00 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -48,5 +48,11 @@ #define USE_IMPROV #endif +#ifdef ARDUINO_ARCH_ESP8266 +#define USE_SOCKET_IMPL_LWIP_TCP +#else +#define USE_SOCKET_IMPL_BSD_SOCKETS +#endif + // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. diff --git a/script/ci-custom.py b/script/ci-custom.py index 5dad3e2445..cdc450a96b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -261,7 +261,7 @@ def highlight(s): @lint_re_check( r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, include=cpp_include, - exclude=["esphome/core/log.h"], + exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"], ) def lint_no_defines(fname, match): s = highlight( @@ -493,7 +493,10 @@ def lint_relative_py_import(fname): "esphome/components/*.h", "esphome/components/*.cpp", "esphome/components/*.tcc", - ] + ], + exclude=[ + "esphome/components/socket/headers.h", + ], ) def lint_namespace(fname, content): expected_name = re.match( From fa2eb46cd6d3e5e38ebd60c65055aa836f2a4aab Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 16:11:09 +1200 Subject: [PATCH 187/285] Allow .yml files to show up in the dashboard (#2257) --- esphome/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 56bc97ca71..527e370ad8 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -260,8 +260,8 @@ def filter_yaml_files(files): f for f in files if ( - os.path.splitext(f)[1] == ".yaml" - and os.path.basename(f) != "secrets.yaml" + os.path.splitext(f)[1] in (".yaml", ".yml") + and os.path.basename(f) not in ("secrets.yaml", "secrets.yml") and not os.path.basename(f).startswith(".") ) ] From f87a701b28ac7164895e0968a4dfef4736fbbf4a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 17:14:12 +1200 Subject: [PATCH 188/285] Bump dashboard to 20210908.0 and fix card names for yml (#2258) --- esphome/dashboard/dashboard.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ab9dd39735..e44ee64770 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -431,7 +431,7 @@ class DashboardEntry: @property def name(self): if self.storage is None: - return self.filename[: -len(".yaml")] + return self.filename.removesuffix(".yml").removesuffix(".yaml") return self.storage.name @property diff --git a/requirements.txt b/requirements.txt index 79fefbfcaa..daaf86e641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==3.1 click==7.1.2 -esphome-dashboard==20210826.0 +esphome-dashboard==20210908.0 From 4356581db0d0e11d34f194961d3b823de5b198bf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Sep 2021 19:51:20 +1200 Subject: [PATCH 189/285] Remove removesuffix --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index e44ee64770..97f9d60693 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -431,7 +431,7 @@ class DashboardEntry: @property def name(self): if self.storage is None: - return self.filename.removesuffix(".yml").removesuffix(".yaml") + return self.filename.replace(".yml", "").replace(".yaml", "") return self.storage.name @property From e44f447d8520833930677892e34842e084715753 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 12:02:32 +0200 Subject: [PATCH 190/285] Fix socket not setting callbacks early enough (#2260) --- .../components/socket/lwip_raw_tcp_impl.cpp | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index a5afea072e..aaeee7268a 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -16,19 +16,28 @@ namespace esphome { namespace socket { -static const char *const TAG = "lwip"; +static const char *const TAG = "socket.lwip"; + +// set to 1 to enable verbose lwip logging +#if 0 +#define LWIP_LOG(msg, ...) ESP_LOGVV(TAG, "socket %p: " msg, this, ##__VA_ARGS__) +#else +#define LWIP_LOG(msg, ...) +#endif class LWIPRawImpl : public Socket { public: LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} ~LWIPRawImpl() override { if (pcb_ != nullptr) { + LWIP_LOG("tcp_abort(%p)", pcb_); tcp_abort(pcb_); pcb_ = nullptr; } } void init() { + LWIP_LOG("init(%p)", pcb_); tcp_arg(pcb_, this); tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); tcp_recv(pcb_, LWIPRawImpl::s_recv_fn); @@ -49,7 +58,7 @@ class LWIPRawImpl : public Socket { if (addr != nullptr) { sock->getpeername(addr, addrlen); } - sock->init(); + LWIP_LOG("accept(%p)", sock.get()); return std::unique_ptr(std::move(sock)); } int bind(const struct sockaddr *name, socklen_t addrlen) override { @@ -97,6 +106,7 @@ class LWIPRawImpl : public Socket { port = ntohs(addr4->sin_port); ip.addr = addr4->sin_addr.s_addr; #endif + LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { errno = EADDRINUSE; @@ -117,6 +127,7 @@ class LWIPRawImpl : public Socket { errno = EBADF; return -1; } + LWIP_LOG("tcp_close(%p)", pcb_); err_t err = tcp_close(pcb_); if (err != ERR_OK) { tcp_abort(pcb_); @@ -143,6 +154,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } + LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); if (err != ERR_OK) { errno = err == ERR_MEM ? ENOMEM : EIO; @@ -178,8 +190,8 @@ class LWIPRawImpl : public Socket { } char buffer[24]; uint32_t ip4 = pcb_->remote_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, - (ip4 >> 0) & 0xFF); + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, + (ip4 >> 24) & 0xFF); return std::string(buffer); } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { @@ -209,8 +221,8 @@ class LWIPRawImpl : public Socket { } char buffer[24]; uint32_t ip4 = pcb_->local_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 24) & 0xFF, (ip4 >> 16) & 0xFF, (ip4 >> 8) & 0xFF, - (ip4 >> 0) & 0xFF); + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, + (ip4 >> 24) & 0xFF); return std::string(buffer); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { @@ -284,6 +296,7 @@ class LWIPRawImpl : public Socket { errno = EBADF; return -1; } + LWIP_LOG("tcp_listen_with_backlog(%p backlog=%d)", pcb_, backlog); struct tcp_pcb *listen_pcb = tcp_listen_with_backlog(pcb_, backlog); if (listen_pcb == nullptr) { tcp_abort(pcb_); @@ -294,6 +307,7 @@ class LWIPRawImpl : public Socket { // tcp_listen reallocates the pcb, replace ours pcb_ = listen_pcb; // set callbacks on new pcb + LWIP_LOG("tcp_arg(%p)", pcb_); tcp_arg(pcb_, this); tcp_accept(pcb_, LWIPRawImpl::s_accept_fn); return 0; @@ -342,6 +356,7 @@ class LWIPRawImpl : public Socket { } else { rx_buf_offset_ += copysize; } + LWIP_LOG("tcp_recved(%p %u)", pcb_, copysize); tcp_recved(pcb_, copysize); buf8 += copysize; @@ -368,6 +383,7 @@ class LWIPRawImpl : public Socket { return -1; } size_t to_send = std::min((size_t) space, len); + LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_MEM) { errno = EWOULDBLOCK; @@ -377,6 +393,7 @@ class LWIPRawImpl : public Socket { errno = EIO; return -1; } + LWIP_LOG("tcp_output(%p)", pcb_); err = tcp_output(pcb_); if (err != ERR_OK) { errno = EIO; @@ -398,6 +415,7 @@ class LWIPRawImpl : public Socket { } err_t accept_fn(struct tcp_pcb *newpcb, err_t err) { + LWIP_LOG("accept(newpcb=%p err=%d)", newpcb, err); if (err != ERR_OK || newpcb == nullptr) { // "An error code if there has been an error accepting. Only return ERR_ABRT if you have // called tcp_abort from within the callback function!" @@ -405,10 +423,13 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - accepted_sockets_.emplace(new LWIPRawImpl(newpcb)); + auto *sock = new LWIPRawImpl(newpcb); + sock->init(); + accepted_sockets_.emplace(sock); return ERR_OK; } void err_fn(err_t err) { + LWIP_LOG("err(err=%d)", err); // "If a connection is aborted because of an error, the application is alerted of this event by // the err callback." // pcb is already freed when this callback is called @@ -417,6 +438,7 @@ class LWIPRawImpl : public Socket { pcb_ = nullptr; } err_t recv_fn(struct pbuf *pb, err_t err) { + LWIP_LOG("recv(pb=%p err=%d)", pb, err); if (err != 0) { // "An error code if there has been an error receiving Only return ERR_ABRT if you have // called tcp_abort from within the callback function!" From 2790d72bff8e0ede8649e861e11fbe33a8a07be0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 12:52:57 +0200 Subject: [PATCH 191/285] Convert API to use sockets (#2253) * Socket component * Lint * Lint * Fix esp8266 missing INADDR_ANY * API convert to sockets and frame helper * Fix compile error Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/__init__.py | 2 +- esphome/components/api/api_connection.cpp | 200 +++++-------- esphome/components/api/api_connection.h | 35 +-- esphome/components/api/api_frame_helper.cpp | 294 ++++++++++++++++++++ esphome/components/api/api_frame_helper.h | 103 +++++++ esphome/components/api/api_server.cpp | 70 ++++- esphome/components/api/api_server.h | 11 +- esphome/components/socket/headers.h | 14 +- 8 files changed, 563 insertions(+), 166 deletions(-) create mode 100644 esphome/components/api/api_frame_helper.cpp create mode 100644 esphome/components/api/api_frame_helper.h diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 559f8f649c..fc140dc7d2 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -19,7 +19,7 @@ from esphome.const import ( from esphome.core import coroutine_with_priority DEPENDENCIES = ["network"] -AUTO_LOAD = ["async_tcp"] +AUTO_LOAD = ["socket"] CODEOWNERS = ["@OttoWinter"] api_ns = cg.esphome_ns.namespace("api") diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 99e611be10..bce0b0bab8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" #include "esphome/core/version.h" +#include #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" @@ -18,74 +19,27 @@ namespace api { static const char *const TAG = "api.connection"; -APIConnection::APIConnection(AsyncClient *client, APIServer *parent) - : client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { - this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this); - this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this); - this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); }, - this); - this->client_->onData([](void *s, AsyncClient *c, void *buf, - size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast(buf), len); }, - this); +APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) + : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { + this->proto_write_buffer_.reserve(64); - this->send_buffer_.reserve(64); - this->recv_buffer_.reserve(32); - this->client_info_ = this->client_->remoteIP().toString().c_str(); + helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; +} +void APIConnection::start() { this->last_traffic_ = millis(); -} -APIConnection::~APIConnection() { delete this->client_; } -void APIConnection::on_error_(int8_t error) { this->remove_ = true; } -void APIConnection::on_disconnect_() { this->remove_ = true; } -void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); } -void APIConnection::on_data_(uint8_t *buf, size_t len) { - if (len == 0 || buf == nullptr) + + APIError err = helper_->init(); + if (err != APIError::OK) { + ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno); + remove_ = true; return; - this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len); -} -void APIConnection::parse_recv_buffer_() { - if (this->recv_buffer_.empty() || this->remove_) - return; - - while (!this->recv_buffer_.empty()) { - if (this->recv_buffer_[0] != 0x00) { - ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str()); - this->on_fatal_error(); - return; - } - uint32_t i = 1; - const uint32_t size = this->recv_buffer_.size(); - uint32_t consumed; - auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed); - if (!msg_size_varint.has_value()) - // not enough data there yet - return; - i += consumed; - uint32_t msg_size = msg_size_varint->as_uint32(); - - auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed); - if (!msg_type_varint.has_value()) - // not enough data there yet - return; - i += consumed; - uint32_t msg_type = msg_type_varint->as_uint32(); - - if (size - i < msg_size) - // message body not fully received - return; - - uint8_t *msg = &this->recv_buffer_[i]; - this->read_message(msg_size, msg_type, msg); - if (this->remove_) - return; - // pop front - uint32_t total = i + msg_size; - this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total); - this->last_traffic_ = millis(); } + client_info_ = helper_->getpeername(); + helper_->set_log_info(client_info_); } -void APIConnection::disconnect_client() { - this->client_->close(); +void APIConnection::force_disconnect_client() { + this->helper_->close(); this->remove_ = true; } @@ -93,61 +47,74 @@ void APIConnection::loop() { if (this->remove_) return; - if (this->next_close_) { - this->disconnect_client(); - return; - } - if (!network_is_connected()) { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); return; } - if (this->client_->disconnected()) { - // failsafe for disconnect logic - this->on_disconnect_(); + if (this->next_close_) { + this->helper_->close(); + this->remove_ = true; return; } - this->parse_recv_buffer_(); + + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err); + return; + } + ReadPacketBuffer buffer; + err = helper_->read_packet(&buffer); + if (err == APIError::WOULD_BLOCK) { + // pass + } else if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err); + return; + } else { + this->last_traffic_ = millis(); + // read a packet + this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); + if (this->remove_) + return; + } this->list_entities_iterator_.advance(); this->initial_state_iterator_.advance(); const uint32_t keepalive = 60000; + const uint32_t now = millis(); if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive - if (millis() - this->last_traffic_ > (keepalive * 5) / 2) { + if (now - this->last_traffic_ > (keepalive * 5) / 2) { + this->force_disconnect_client(); ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); - this->disconnect_client(); } - } else if (millis() - this->last_traffic_ > keepalive) { + } else if (now - this->last_traffic_ > keepalive) { this->sent_ping_ = true; this->send_ping_request(PingRequest()); } #ifdef USE_ESP32_CAMERA - if (this->image_reader_.available()) { - uint32_t space = this->client_->space(); - // reserve 15 bytes for metadata, and at least 64 bytes of data - if (space >= 15 + 64) { - uint32_t to_send = std::min(space - 15, this->image_reader_.available()); - auto buffer = this->create_buffer(); - // fixed32 key = 1; - buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); - // bytes data = 2; - buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); - // bool done = 3; - bool done = this->image_reader_.available() == to_send; - buffer.encode_bool(3, done); - bool success = this->send_buffer(buffer, 44); + if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) { + uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available()); + auto buffer = this->create_buffer(); + // fixed32 key = 1; + buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); + // bytes data = 2; + buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); + // bool done = 3; + bool done = this->image_reader_.available() == to_send; + buffer.encode_bool(3, done); + bool success = this->send_buffer(buffer, 44); - if (success) { - this->image_reader_.consume_data(to_send); - } - if (success && done) { - this->image_reader_.return_image(); - } + if (success) { + this->image_reader_.consume_data(to_send); + } + if (success && done) { + this->image_reader_.return_image(); } } #endif @@ -709,8 +676,8 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin } HelloResponse APIConnection::hello(const HelloRequest &msg) { - this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str(); - this->client_info_ += ")"; + this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; + this->helper_->set_log_info(client_info_); ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); HelloResponse resp; @@ -786,44 +753,31 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; + if (!this->helper_->can_write_without_blocking()) + return false; - std::vector header; - header.push_back(0x00); - ProtoVarInt(buffer.get_buffer()->size()).encode(header); - ProtoVarInt(message_type).encode(header); - - size_t needed_space = buffer.get_buffer()->size() + header.size(); - - if (needed_space > this->client_->space()) { - delay(0); - if (needed_space > this->client_->space()) { - // SubscribeLogsResponse - if (message_type != 29) { - ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); - } - delay(0); - return false; - } + APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); + if (err == APIError::WOULD_BLOCK) + return false; + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno); + return false; } - - this->client_->add(reinterpret_cast(header.data()), header.size(), - ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE); - this->client_->add(reinterpret_cast(buffer.get_buffer()->data()), buffer.get_buffer()->size(), - ASYNC_WRITE_FLAG_COPY); - bool ret = this->client_->send(); - return ret; + this->last_traffic_ = millis(); + return true; } void APIConnection::on_unauthenticated_access() { - ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); this->on_fatal_error(); + ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); } void APIConnection::on_no_setup_connection() { - ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); this->on_fatal_error(); + ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); } void APIConnection::on_fatal_error() { ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str()); - this->client_->close(); + this->helper_->close(); this->remove_ = true; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index bc9839a423..a1788bbede 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -5,16 +5,18 @@ #include "api_pb2.h" #include "api_pb2_service.h" #include "api_server.h" +#include "api_frame_helper.h" namespace esphome { namespace api { class APIConnection : public APIServerConnection { public: - APIConnection(AsyncClient *client, APIServer *parent); - virtual ~APIConnection(); + APIConnection(std::unique_ptr socket, APIServer *parent); + virtual ~APIConnection() = default; - void disconnect_client(); + void start(); + void force_disconnect_client(); void loop(); bool send_list_info_done() { @@ -87,8 +89,8 @@ class APIConnection : public APIServerConnection { #endif void on_disconnect_response(const DisconnectResponse &value) override { - // we initiated disconnect_client - this->next_close_ = true; + this->helper_->close(); + this->remove_ = true; } void on_ping_response(const PingResponse &value) override { // we initiated ping @@ -102,6 +104,8 @@ class APIConnection : public APIServerConnection { ConnectResponse connect(const ConnectRequest &msg) override; DisconnectResponse disconnect(const DisconnectRequest &msg) override { // remote initiated disconnect_client + // don't close yet, we still need to send the disconnect response + // close will happen on next loop this->next_close_ = true; DisconnectResponse resp; return resp; @@ -135,19 +139,16 @@ class APIConnection : public APIServerConnection { void on_unauthenticated_access() override; void on_no_setup_connection() override; ProtoWriteBuffer create_buffer() override { - this->send_buffer_.clear(); - return {&this->send_buffer_}; + // FIXME: ensure no recursive writes can happen + this->proto_write_buffer_.clear(); + return {&this->proto_write_buffer_}; } bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override; protected: friend APIServer; - void on_error_(int8_t error); - void on_disconnect_(); - void on_timeout_(uint32_t time); - void on_data_(uint8_t *buf, size_t len); - void parse_recv_buffer_(); + bool send_(const void *buf, size_t len, bool force); enum class ConnectionState { WAITING_FOR_HELLO, @@ -157,8 +158,10 @@ class APIConnection : public APIServerConnection { bool remove_{false}; - std::vector send_buffer_; - std::vector recv_buffer_; + // Buffer used to encode proto messages + // Re-use to prevent allocations + std::vector proto_write_buffer_; + std::unique_ptr helper_; std::string client_info_; #ifdef USE_ESP32_CAMERA @@ -170,9 +173,7 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; - bool current_nodelay_{false}; - bool next_close_{false}; - AsyncClient *client_; + bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp new file mode 100644 index 0000000000..f903ab8656 --- /dev/null +++ b/esphome/components/api/api_frame_helper.cpp @@ -0,0 +1,294 @@ +#include "api_frame_helper.h" + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "proto.h" + +namespace esphome { +namespace api { + +static const char *const TAG = "api.socket"; + +/// Is the given return value (from read/write syscalls) a wouldblock error? +bool is_would_block(ssize_t ret) { + if (ret == -1) { + return errno == EWOULDBLOCK || errno == EAGAIN; + } + return ret == 0; +} + +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) + +/// Initialize the frame helper, returns OK if successful. +APIError APIPlaintextFrameHelper::init() { + if (state_ != State::INITIALIZE || socket_ == nullptr) { + HELPER_LOG("Bad state for init %d", (int) state_); + return APIError::BAD_STATE; + } + int err = socket_->setblocking(false); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nonblocking failed with errno %d", errno); + return APIError::TCP_NONBLOCKING_FAILED; + } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + + state_ = State::DATA; + return APIError::OK; +} +/// Not used for plaintext +APIError APIPlaintextFrameHelper::loop() { + if (state_ != State::DATA) { + return APIError::BAD_STATE; + } + // try send pending TX data + if (!tx_buf_.empty()) { + APIError err = try_send_tx_buf_(); + if (err != APIError::OK) { + return err; + } + } + return APIError::OK; +} + +/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter + * + * @param frame: The struct to hold the frame information in. + * msg: store the parsed frame in that struct + * + * @return See APIError + * + * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. + */ +APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { + int err; + APIError aerr; + + if (frame == nullptr) { + HELPER_LOG("Bad argument for try_read_frame_"); + return APIError::BAD_ARG; + } + + // read header + while (!rx_header_parsed_) { + uint8_t data; + ssize_t received = socket_->read(&data, 1); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_header_buf_.push_back(data); + + // try parse header + if (rx_header_buf_[0] != 0x00) { + state_ = State::FAILED; + HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); + return APIError::BAD_INDICATOR; + } + + size_t i = 1; + size_t consumed = 0; + auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); + if (!msg_size_varint.has_value()) { + // not enough data there yet + continue; + } + + i += consumed; + rx_header_parsed_len_ = msg_size_varint->as_uint32(); + + auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); + if (!msg_type_varint.has_value()) { + // not enough data there yet + continue; + } + rx_header_parsed_type_ = msg_type_varint->as_uint32(); + rx_header_parsed_ = true; + } + // header reading done + + // reserve space for body + if (rx_buf_.size() != rx_header_parsed_len_) { + rx_buf_.resize(rx_header_parsed_len_); + } + + if (rx_buf_len_ < rx_header_parsed_len_) { + // more data to read + size_t to_read = rx_header_parsed_len_ - rx_buf_len_; + ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_buf_len_ += received; + if (received != to_read) { + // not all read + return APIError::WOULD_BLOCK; + } + } + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + frame->msg = std::move(rx_buf_); + // consume msg + rx_buf_ = {}; + rx_buf_len_ = 0; + rx_header_buf_.clear(); + rx_header_parsed_ = false; + return APIError::OK; +} + +APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { + int err; + APIError aerr; + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + + buffer->container = std::move(frame.msg); + buffer->data_offset = 0; + buffer->data_len = rx_header_parsed_len_; + buffer->type = rx_header_parsed_type_; + return APIError::OK; +} +bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } +APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { + int err; + APIError aerr; + + if (state_ != State::DATA) { + return APIError::BAD_STATE; + } + + std::vector header; + header.push_back(0x00); + ProtoVarInt(payload_len).encode(header); + ProtoVarInt(type).encode(header); + + aerr = write_raw_(&header[0], header.size()); + if (aerr != APIError::OK) { + return aerr; + } + aerr = write_raw_(payload, payload_len); + if (aerr != APIError::OK) { + return aerr; + } + return APIError::OK; +} +APIError APIPlaintextFrameHelper::try_send_tx_buf_() { + // try send from tx_buf + while (state_ != State::CLOSED && !tx_buf_.empty()) { + ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); + if (sent == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent == 0) { + break; + } + // TODO: inefficient if multiple packets in txbuf + // replace with deque of buffers + tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent); + } + + return APIError::OK; +} +/** Write the data to the socket, or buffer it a write would block + * + * @param data The data to write + * @param len The length of data + */ +APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { + if (len == 0) + return APIError::OK; + int err; + APIError aerr; + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + + if (!tx_buf_.empty()) { + // try to empty tx_buf_ first + aerr = try_send_tx_buf_(); + if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK) + return aerr; + } + + if (!tx_buf_.empty()) { + // tx buf not empty, can't write now because then stream would be inconsistent + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } + + ssize_t sent = socket_->write(data, len); + if (is_would_block(sent)) { + // operation would block, add buffer to tx_buf + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } else if (sent == -1) { + // an error occured + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent != len) { + // partially sent, add end to tx_buf + tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + return APIError::OK; + } + // fully sent + return APIError::OK; +} +APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) { + APIError aerr; + + uint8_t header[3]; + header[0] = 0x01; // indicator + header[1] = (uint8_t)(len >> 8); + header[2] = (uint8_t) len; + + aerr = write_raw_(header, 3); + if (aerr != APIError::OK) + return aerr; + aerr = write_raw_(data, len); + return aerr; +} + +APIError APIPlaintextFrameHelper::close() { + state_ = State::CLOSED; + int err = socket_->close(); + if (err == -1) + return APIError::CLOSE_FAILED; + return APIError::OK; +} +APIError APIPlaintextFrameHelper::shutdown(int how) { + int err = socket_->shutdown(how); + if (err == -1) + return APIError::SHUTDOWN_FAILED; + if (how == SHUT_RDWR) { + state_ = State::CLOSED; + } + return APIError::OK; +} + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h new file mode 100644 index 0000000000..14a0760c25 --- /dev/null +++ b/esphome/components/api/api_frame_helper.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include + +#include "esphome/core/defines.h" + +#include "esphome/components/socket/socket.h" + +namespace esphome { +namespace api { + +struct ReadPacketBuffer { + std::vector container; + uint16_t type; + size_t data_offset; + size_t data_len; +}; + +struct PacketBuffer { + const std::vector container; + uint16_t type; + uint8_t data_offset; + uint8_t data_len; +}; + +enum class APIError : int { + OK = 0, + WOULD_BLOCK = 1001, + BAD_INDICATOR = 1003, + BAD_DATA_PACKET = 1004, + TCP_NODELAY_FAILED = 1005, + TCP_NONBLOCKING_FAILED = 1006, + CLOSE_FAILED = 1007, + SHUTDOWN_FAILED = 1008, + BAD_STATE = 1009, + BAD_ARG = 1010, + SOCKET_READ_FAILED = 1011, + SOCKET_WRITE_FAILED = 1012, + OUT_OF_MEMORY = 1018, +}; + +class APIFrameHelper { + public: + virtual APIError init() = 0; + virtual APIError loop() = 0; + virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; + virtual bool can_write_without_blocking() = 0; + virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0; + virtual std::string getpeername() = 0; + virtual APIError close() = 0; + virtual APIError shutdown(int how) = 0; + // Give this helper a name for logging + virtual void set_log_info(std::string info) = 0; +}; +class APIPlaintextFrameHelper : public APIFrameHelper { + public: + APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} + ~APIPlaintextFrameHelper() = default; + APIError init() override; + APIError loop() override; + APIError read_packet(ReadPacketBuffer *buffer) override; + bool can_write_without_blocking() override; + APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; + std::string getpeername() override { return socket_->getpeername(); } + APIError close() override; + APIError shutdown(int how) override; + // Give this helper a name for logging + void set_log_info(std::string info) override { info_ = std::move(info); } + + protected: + struct ParsedFrame { + std::vector msg; + }; + + APIError try_read_frame_(ParsedFrame *frame); + APIError try_send_tx_buf_(); + APIError write_frame_(const uint8_t *data, size_t len); + APIError write_raw_(const uint8_t *data, size_t len); + + std::unique_ptr socket_; + + std::string info_; + std::vector rx_header_buf_; + bool rx_header_parsed_ = false; + uint32_t rx_header_parsed_type_ = 0; + uint32_t rx_header_parsed_len_ = 0; + + std::vector rx_buf_; + size_t rx_buf_len_ = 0; + + std::vector tx_buf_; + + enum class State { + INITIALIZE = 1, + DATA = 2, + CLOSED = 3, + FAILED = 4, + } state_ = State::INITIALIZE; +}; + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d48c0a4fd8..c4c193b389 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -1,10 +1,11 @@ #include "api_server.h" #include "api_connection.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" -#include "esphome/core/util.h" #include "esphome/core/defines.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" #include "esphome/core/version.h" +#include #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -21,20 +22,45 @@ static const char *const TAG = "api"; void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); - this->server_ = AsyncServer(this->port_); - this->server_.setNoDelay(false); - this->server_.begin(); - this->server_.onClient( - [](void *s, AsyncClient *client) { - if (client == nullptr) - return; + socket_ = socket::socket(AF_INET, SOCK_STREAM, 0); + if (socket_ == nullptr) { + ESP_LOGW(TAG, "Could not create socket."); + this->mark_failed(); + return; + } + int enable = 1; + int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); + // we can still continue + } + err = socket_->setblocking(false); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + this->mark_failed(); + return; + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ESPHOME_INADDR_ANY; + server.sin_port = htons(this->port_); + + err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + this->mark_failed(); + return; + } + + err = socket_->listen(4); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); + this->mark_failed(); + return; + } - // can't print here because in lwIP thread - // ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str()); - auto *a_this = (APIServer *) s; - a_this->clients_.push_back(new APIConnection(client, a_this)); - }, - this); #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { @@ -59,6 +85,20 @@ void APIServer::setup() { #endif } void APIServer::loop() { + // Accept new clients + while (true) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len); + if (!sock) + break; + ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); + + auto *conn = new APIConnection(std::move(sock), this); + clients_.push_back(conn); + conn->start(); + } + // Partition clients into remove and active auto new_end = std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 96b3192e9e..7c42fe7dd5 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -4,6 +4,7 @@ #include "esphome/core/controller.h" #include "esphome/core/defines.h" #include "esphome/core/log.h" +#include "esphome/components/socket/socket.h" #include "api_pb2.h" #include "api_pb2_service.h" #include "util.h" @@ -11,13 +12,6 @@ #include "subscribe_state.h" #include "user_services.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif - namespace esphome { namespace api { @@ -35,6 +29,7 @@ class APIServer : public Component, public Controller { void set_port(uint16_t port); void set_password(const std::string &password); void set_reboot_timeout(uint32_t reboot_timeout); + void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; @@ -86,7 +81,7 @@ class APIServer : public Component, public Controller { const std::vector &get_user_services() const { return this->user_services_; } protected: - AsyncServer server_{0}; + std::unique_ptr socket_ = nullptr; uint16_t port_{6053}; uint32_t reboot_timeout_{300000}; uint32_t last_connected_{0}; diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index a084823bdf..da710b760e 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -90,7 +90,11 @@ typedef uint32_t socklen_t; #undef INADDR_NONE #endif -#define INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) +#else // !ARDUINO_ARCH_ESP8266 +#define ESPHOME_INADDR_ANY INADDR_ANY +#define ESPHOME_INADDR_NONE INADDR_NONE #endif #endif // USE_SOCKET_IMPL_LWIP_TCP @@ -112,6 +116,12 @@ typedef uint32_t socklen_t; #endif // not defined for ESP32 typedef uint32_t socklen_t; -#endif // ARDUINO_ARCH_ESP32 + +#define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) +#define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) +#else // !ARDUINO_ARCH_ESP32 +#define ESPHOME_INADDR_ANY INADDR_ANY +#define ESPHOME_INADDR_NONE INADDR_NONE +#endif #endif // USE_SOCKET_IMPL_BSD_SOCKETS From 4e120a291eb4ca444dfd6a8a488fa6700c554579 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 9 Sep 2021 01:10:02 +0400 Subject: [PATCH 192/285] Midea support v2 (#2188) --- CODEOWNERS | 3 +- esphome/components/climate/__init__.py | 1 + esphome/components/climate/climate.cpp | 69 +++++ esphome/components/climate/climate.h | 13 + esphome/components/climate/climate_traits.h | 2 + esphome/components/midea/__init__.py | 0 esphome/components/midea/adapter.cpp | 173 +++++++++++ esphome/components/midea/adapter.h | 42 +++ esphome/components/midea/air_conditioner.cpp | 152 ++++++++++ esphome/components/midea/air_conditioner.h | 41 +++ esphome/components/midea/appliance_base.h | 76 +++++ esphome/components/midea/automations.h | 56 ++++ esphome/components/midea/climate.py | 284 ++++++++++++++++++ esphome/components/midea/midea_ir.h | 42 +++ esphome/components/midea_ac/climate.py | 114 +------ esphome/components/midea_ac/midea_climate.cpp | 208 ------------- esphome/components/midea_ac/midea_climate.h | 68 ----- esphome/components/midea_ac/midea_frame.cpp | 238 --------------- esphome/components/midea_ac/midea_frame.h | 165 ---------- esphome/components/midea_dongle/__init__.py | 30 -- .../components/midea_dongle/midea_dongle.cpp | 98 ------ .../components/midea_dongle/midea_dongle.h | 56 ---- .../components/midea_dongle/midea_frame.cpp | 95 ------ esphome/components/midea_dongle/midea_frame.h | 104 ------- esphome/components/remote_base/__init__.py | 42 +++ .../components/remote_base/midea_protocol.cpp | 99 ++++++ .../components/remote_base/midea_protocol.h | 105 +++++++ esphome/const.py | 6 + esphome/core/component.cpp | 1 + esphome/core/component.h | 2 + platformio.ini | 1 + tests/test1.yaml | 79 ++++- tests/test3.yaml | 37 --- 33 files changed, 1276 insertions(+), 1226 deletions(-) create mode 100644 esphome/components/midea/__init__.py create mode 100644 esphome/components/midea/adapter.cpp create mode 100644 esphome/components/midea/adapter.h create mode 100644 esphome/components/midea/air_conditioner.cpp create mode 100644 esphome/components/midea/air_conditioner.h create mode 100644 esphome/components/midea/appliance_base.h create mode 100644 esphome/components/midea/automations.h create mode 100644 esphome/components/midea/climate.py create mode 100644 esphome/components/midea/midea_ir.h delete mode 100644 esphome/components/midea_ac/midea_climate.cpp delete mode 100644 esphome/components/midea_ac/midea_climate.h delete mode 100644 esphome/components/midea_ac/midea_frame.cpp delete mode 100644 esphome/components/midea_ac/midea_frame.h delete mode 100644 esphome/components/midea_dongle/__init__.py delete mode 100644 esphome/components/midea_dongle/midea_dongle.cpp delete mode 100644 esphome/components/midea_dongle/midea_dongle.h delete mode 100644 esphome/components/midea_dongle/midea_frame.cpp delete mode 100644 esphome/components/midea_dongle/midea_frame.h create mode 100644 esphome/components/remote_base/midea_protocol.cpp create mode 100644 esphome/components/remote_base/midea_protocol.h diff --git a/CODEOWNERS b/CODEOWNERS index ad670ede14..dedd8acc1e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -79,8 +79,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn -esphome/components/midea_ac/* @dudanov -esphome/components/midea_dongle/* @dudanov +esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index f6a9fa2927..c2f07ce423 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -63,6 +63,7 @@ validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) ClimatePreset = climate_ns.enum("ClimatePreset") CLIMATE_PRESETS = { + "NONE": ClimatePreset.CLIMATE_PRESET_NONE, "ECO": ClimatePreset.CLIMATE_PRESET_ECO, "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 8da2206f37..4861e7b8cb 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -494,5 +494,74 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { climate->publish_state(); } +template bool set_alternative(optional &dst, optional &alt, const T1 &src) { + bool is_changed = alt.has_value(); + alt.reset(); + if (is_changed || dst != src) { + dst = src; + is_changed = true; + } + return is_changed; +} + +bool Climate::set_fan_mode_(ClimateFanMode mode) { + return set_alternative(this->fan_mode, this->custom_fan_mode, mode); +} + +bool Climate::set_custom_fan_mode_(const std::string &mode) { + return set_alternative(this->custom_fan_mode, this->fan_mode, mode); +} + +bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); } + +bool Climate::set_custom_preset_(const std::string &preset) { + return set_alternative(this->custom_preset, this->preset, preset); +} + +void Climate::dump_traits_(const char *tag) { + auto traits = this->get_traits(); + ESP_LOGCONFIG(tag, "ClimateTraits:"); + ESP_LOGCONFIG(tag, " [x] Visual settings:"); + ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature()); + ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature()); + ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step()); + if (traits.get_supports_current_temperature()) + ESP_LOGCONFIG(tag, " [x] Supports current temperature"); + if (traits.get_supports_two_point_target_temperature()) + ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature"); + if (traits.get_supports_action()) + ESP_LOGCONFIG(tag, " [x] Supports action"); + if (!traits.get_supported_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported modes:"); + for (ClimateMode m : traits.get_supported_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m)); + } + if (!traits.get_supported_fan_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported fan modes:"); + for (ClimateFanMode m : traits.get_supported_fan_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m)); + } + if (!traits.get_supported_custom_fan_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:"); + for (const std::string &s : traits.get_supported_custom_fan_modes()) + ESP_LOGCONFIG(tag, " - %s", s.c_str()); + } + if (!traits.get_supported_presets().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported presets:"); + for (ClimatePreset p : traits.get_supported_presets()) + ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p)); + } + if (!traits.get_supported_custom_presets().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported custom presets:"); + for (const std::string &s : traits.get_supported_custom_presets()) + ESP_LOGCONFIG(tag, " - %s", s.c_str()); + } + if (!traits.get_supported_swing_modes().empty()) { + ESP_LOGCONFIG(tag, " [x] Supported swing modes:"); + for (ClimateSwingMode m : traits.get_supported_swing_modes()) + ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m)); + } +} + } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index b208e5946a..46d0fb1d77 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -245,6 +245,18 @@ class Climate : public Nameable { protected: friend ClimateCall; + /// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed. + bool set_fan_mode_(ClimateFanMode mode); + + /// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed. + bool set_custom_fan_mode_(const std::string &mode); + + /// Set preset. Reset custom preset. Return true if preset has been changed. + bool set_preset_(ClimatePreset preset); + + /// Set custom preset. Reset primary preset. Return true if preset has been changed. + bool set_custom_preset_(const std::string &preset); + /** Get the default traits of this climate device. * * Traits are static data that encode the capabilities and static data for a climate device such as supported @@ -270,6 +282,7 @@ class Climate : public Nameable { void save_state_(); uint32_t hash_base() override; + void dump_traits_(const char *tag); CallbackManager state_callback_{}; ESPPreferenceObject rtc_; diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 48493b500c..903ce085d8 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -72,6 +72,7 @@ class ClimateTraits { void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); } ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20") @@ -104,6 +105,7 @@ class ClimateTraits { void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); } bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } bool get_supports_presets() const { return !supported_presets_.empty(); } const std::set &get_supported_presets() const { return supported_presets_; } diff --git a/esphome/components/midea/__init__.py b/esphome/components/midea/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/adapter.cpp new file mode 100644 index 0000000000..bd5b289095 --- /dev/null +++ b/esphome/components/midea/adapter.cpp @@ -0,0 +1,173 @@ +#include "esphome/core/log.h" +#include "adapter.h" + +namespace esphome { +namespace midea { + +const char *const Constants::TAG = "midea"; +const std::string Constants::FREEZE_PROTECTION = "freeze protection"; +const std::string Constants::SILENT = "silent"; +const std::string Constants::TURBO = "turbo"; + +ClimateMode Converters::to_climate_mode(MideaMode mode) { + switch (mode) { + case MideaMode::MODE_AUTO: + return ClimateMode::CLIMATE_MODE_HEAT_COOL; + case MideaMode::MODE_COOL: + return ClimateMode::CLIMATE_MODE_COOL; + case MideaMode::MODE_DRY: + return ClimateMode::CLIMATE_MODE_DRY; + case MideaMode::MODE_FAN_ONLY: + return ClimateMode::CLIMATE_MODE_FAN_ONLY; + case MideaMode::MODE_HEAT: + return ClimateMode::CLIMATE_MODE_HEAT; + default: + return ClimateMode::CLIMATE_MODE_OFF; + } +} + +MideaMode Converters::to_midea_mode(ClimateMode mode) { + switch (mode) { + case ClimateMode::CLIMATE_MODE_HEAT_COOL: + return MideaMode::MODE_AUTO; + case ClimateMode::CLIMATE_MODE_COOL: + return MideaMode::MODE_COOL; + case ClimateMode::CLIMATE_MODE_DRY: + return MideaMode::MODE_DRY; + case ClimateMode::CLIMATE_MODE_FAN_ONLY: + return MideaMode::MODE_FAN_ONLY; + case ClimateMode::CLIMATE_MODE_HEAT: + return MideaMode::MODE_HEAT; + default: + return MideaMode::MODE_OFF; + } +} + +ClimateSwingMode Converters::to_climate_swing_mode(MideaSwingMode mode) { + switch (mode) { + case MideaSwingMode::SWING_VERTICAL: + return ClimateSwingMode::CLIMATE_SWING_VERTICAL; + case MideaSwingMode::SWING_HORIZONTAL: + return ClimateSwingMode::CLIMATE_SWING_HORIZONTAL; + case MideaSwingMode::SWING_BOTH: + return ClimateSwingMode::CLIMATE_SWING_BOTH; + default: + return ClimateSwingMode::CLIMATE_SWING_OFF; + } +} + +MideaSwingMode Converters::to_midea_swing_mode(ClimateSwingMode mode) { + switch (mode) { + case ClimateSwingMode::CLIMATE_SWING_VERTICAL: + return MideaSwingMode::SWING_VERTICAL; + case ClimateSwingMode::CLIMATE_SWING_HORIZONTAL: + return MideaSwingMode::SWING_HORIZONTAL; + case ClimateSwingMode::CLIMATE_SWING_BOTH: + return MideaSwingMode::SWING_BOTH; + default: + return MideaSwingMode::SWING_OFF; + } +} + +MideaFanMode Converters::to_midea_fan_mode(ClimateFanMode mode) { + switch (mode) { + case ClimateFanMode::CLIMATE_FAN_LOW: + return MideaFanMode::FAN_LOW; + case ClimateFanMode::CLIMATE_FAN_MEDIUM: + return MideaFanMode::FAN_MEDIUM; + case ClimateFanMode::CLIMATE_FAN_HIGH: + return MideaFanMode::FAN_HIGH; + default: + return MideaFanMode::FAN_AUTO; + } +} + +ClimateFanMode Converters::to_climate_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_LOW: + return ClimateFanMode::CLIMATE_FAN_LOW; + case MideaFanMode::FAN_MEDIUM: + return ClimateFanMode::CLIMATE_FAN_MEDIUM; + case MideaFanMode::FAN_HIGH: + return ClimateFanMode::CLIMATE_FAN_HIGH; + default: + return ClimateFanMode::CLIMATE_FAN_AUTO; + } +} + +bool Converters::is_custom_midea_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_SILENT: + case MideaFanMode::FAN_TURBO: + return true; + default: + return false; + } +} + +const std::string &Converters::to_custom_climate_fan_mode(MideaFanMode mode) { + switch (mode) { + case MideaFanMode::FAN_SILENT: + return Constants::SILENT; + default: + return Constants::TURBO; + } +} + +MideaFanMode Converters::to_midea_fan_mode(const std::string &mode) { + if (mode == Constants::SILENT) + return MideaFanMode::FAN_SILENT; + return MideaFanMode::FAN_TURBO; +} + +MideaPreset Converters::to_midea_preset(ClimatePreset preset) { + switch (preset) { + case ClimatePreset::CLIMATE_PRESET_SLEEP: + return MideaPreset::PRESET_SLEEP; + case ClimatePreset::CLIMATE_PRESET_ECO: + return MideaPreset::PRESET_ECO; + case ClimatePreset::CLIMATE_PRESET_BOOST: + return MideaPreset::PRESET_TURBO; + default: + return MideaPreset::PRESET_NONE; + } +} + +ClimatePreset Converters::to_climate_preset(MideaPreset preset) { + switch (preset) { + case MideaPreset::PRESET_SLEEP: + return ClimatePreset::CLIMATE_PRESET_SLEEP; + case MideaPreset::PRESET_ECO: + return ClimatePreset::CLIMATE_PRESET_ECO; + case MideaPreset::PRESET_TURBO: + return ClimatePreset::CLIMATE_PRESET_BOOST; + default: + return ClimatePreset::CLIMATE_PRESET_NONE; + } +} + +bool Converters::is_custom_midea_preset(MideaPreset preset) { return preset == MideaPreset::PRESET_FREEZE_PROTECTION; } + +const std::string &Converters::to_custom_climate_preset(MideaPreset preset) { return Constants::FREEZE_PROTECTION; } + +MideaPreset Converters::to_midea_preset(const std::string &preset) { return MideaPreset::PRESET_FREEZE_PROTECTION; } + +void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities) { + if (capabilities.supportAutoMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT_COOL); + if (capabilities.supportCoolMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_COOL); + if (capabilities.supportHeatMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_HEAT); + if (capabilities.supportDryMode()) + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_DRY); + if (capabilities.supportTurboPreset()) + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_BOOST); + if (capabilities.supportEcoPreset()) + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_ECO); + if (capabilities.supportFrostProtectionPreset()) + traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); +} + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/adapter.h new file mode 100644 index 0000000000..8d8d57e8f9 --- /dev/null +++ b/esphome/components/midea/adapter.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include "esphome/components/climate/climate_traits.h" +#include "appliance_base.h" + +namespace esphome { +namespace midea { + +using MideaMode = dudanov::midea::ac::Mode; +using MideaSwingMode = dudanov::midea::ac::SwingMode; +using MideaFanMode = dudanov::midea::ac::FanMode; +using MideaPreset = dudanov::midea::ac::Preset; + +class Constants { + public: + static const char *const TAG; + static const std::string FREEZE_PROTECTION; + static const std::string SILENT; + static const std::string TURBO; +}; + +class Converters { + public: + static MideaMode to_midea_mode(ClimateMode mode); + static ClimateMode to_climate_mode(MideaMode mode); + static MideaSwingMode to_midea_swing_mode(ClimateSwingMode mode); + static ClimateSwingMode to_climate_swing_mode(MideaSwingMode mode); + static MideaPreset to_midea_preset(ClimatePreset preset); + static MideaPreset to_midea_preset(const std::string &preset); + static bool is_custom_midea_preset(MideaPreset preset); + static ClimatePreset to_climate_preset(MideaPreset preset); + static const std::string &to_custom_climate_preset(MideaPreset preset); + static MideaFanMode to_midea_fan_mode(ClimateFanMode fan_mode); + static MideaFanMode to_midea_fan_mode(const std::string &fan_mode); + static bool is_custom_midea_fan_mode(MideaFanMode fan_mode); + static ClimateFanMode to_climate_fan_mode(MideaFanMode fan_mode); + static const std::string &to_custom_climate_fan_mode(MideaFanMode fan_mode); + static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp new file mode 100644 index 0000000000..a71f1dbdfb --- /dev/null +++ b/esphome/components/midea/air_conditioner.cpp @@ -0,0 +1,152 @@ +#include "esphome/core/log.h" +#include "air_conditioner.h" +#include "adapter.h" +#ifdef USE_REMOTE_TRANSMITTER +#include "midea_ir.h" +#endif + +namespace esphome { +namespace midea { + +static void set_sensor(Sensor *sensor, float value) { + if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) + sensor->publish_state(value); +} + +template void update_property(T &property, const T &value, bool &flag) { + if (property != value) { + property = value; + flag = true; + } +} + +void AirConditioner::on_status_change() { + bool need_publish = false; + update_property(this->target_temperature, this->base_.getTargetTemp(), need_publish); + update_property(this->current_temperature, this->base_.getIndoorTemp(), need_publish); + auto mode = Converters::to_climate_mode(this->base_.getMode()); + update_property(this->mode, mode, need_publish); + auto swing_mode = Converters::to_climate_swing_mode(this->base_.getSwingMode()); + update_property(this->swing_mode, swing_mode, need_publish); + // Preset + auto preset = this->base_.getPreset(); + if (Converters::is_custom_midea_preset(preset)) { + if (this->set_custom_preset_(Converters::to_custom_climate_preset(preset))) + need_publish = true; + } else if (this->set_preset_(Converters::to_climate_preset(preset))) { + need_publish = true; + } + // Fan mode + auto fan_mode = this->base_.getFanMode(); + if (Converters::is_custom_midea_fan_mode(fan_mode)) { + if (this->set_custom_fan_mode_(Converters::to_custom_climate_fan_mode(fan_mode))) + need_publish = true; + } else if (this->set_fan_mode_(Converters::to_climate_fan_mode(fan_mode))) { + need_publish = true; + } + if (need_publish) + this->publish_state(); + set_sensor(this->outdoor_sensor_, this->base_.getOutdoorTemp()); + set_sensor(this->power_sensor_, this->base_.getPowerUsage()); + set_sensor(this->humidity_sensor_, this->base_.getIndoorHum()); +} + +void AirConditioner::control(const ClimateCall &call) { + dudanov::midea::ac::Control ctrl{}; + if (call.get_target_temperature().has_value()) + ctrl.targetTemp = call.get_target_temperature().value(); + if (call.get_swing_mode().has_value()) + ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value()); + if (call.get_mode().has_value()) + ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); + if (call.get_preset().has_value()) + ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); + else if (call.get_custom_preset().has_value()) + ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); + if (call.get_fan_mode().has_value()) + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); + else if (call.get_custom_fan_mode().has_value()) + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); + this->base_.control(ctrl); +} + +ClimateTraits AirConditioner::traits() { + auto traits = ClimateTraits(); + traits.set_supports_current_temperature(true); + traits.set_visual_min_temperature(17); + traits.set_visual_max_temperature(30); + traits.set_visual_temperature_step(0.5); + traits.set_supported_modes(this->supported_modes_); + traits.set_supported_swing_modes(this->supported_swing_modes_); + traits.set_supported_presets(this->supported_presets_); + traits.set_supported_custom_presets(this->supported_custom_presets_); + traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_); + /* + MINIMAL SET OF CAPABILITIES */ + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF); + traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM); + traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH); + traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF); + traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL); + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE); + traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP); + if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) + Converters::to_climate_traits(traits, this->base_.getCapabilities()); + return traits; +} + +void AirConditioner::dump_config() { + ESP_LOGCONFIG(Constants::TAG, "MideaDongle:"); + ESP_LOGCONFIG(Constants::TAG, " [x] Period: %dms", this->base_.getPeriod()); + ESP_LOGCONFIG(Constants::TAG, " [x] Response timeout: %dms", this->base_.getTimeout()); + ESP_LOGCONFIG(Constants::TAG, " [x] Request attempts: %d", this->base_.getNumAttempts()); +#ifdef USE_REMOTE_TRANSMITTER + ESP_LOGCONFIG(Constants::TAG, " [x] Using RemoteTransmitter"); +#endif + if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) { + this->base_.getCapabilities().dump(); + } else if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_ERROR) { + ESP_LOGW(Constants::TAG, + "Failed to get 0xB5 capabilities report. Suggest to disable it in config and manually set your " + "appliance options."); + } + this->dump_traits_(Constants::TAG); +} + +/* ACTIONS */ + +void AirConditioner::do_follow_me(float temperature, bool beeper) { +#ifdef USE_REMOTE_TRANSMITTER + IrFollowMeData data(static_cast(lroundf(temperature)), beeper); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif +} + +void AirConditioner::do_swing_step() { +#ifdef USE_REMOTE_TRANSMITTER + IrSpecialData data(0x01); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif +} + +void AirConditioner::do_display_toggle() { + if (this->base_.getCapabilities().supportLightControl()) { + this->base_.displayToggle(); + } else { +#ifdef USE_REMOTE_TRANSMITTER + IrSpecialData data(0x08); + this->transmit_ir(data); +#else + ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); +#endif + } +} + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h new file mode 100644 index 0000000000..895b6412f3 --- /dev/null +++ b/esphome/components/midea/air_conditioner.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include "appliance_base.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace midea { + +using sensor::Sensor; +using climate::ClimateCall; + +class AirConditioner : public ApplianceBase { + public: + void dump_config() override; + void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; } + void set_humidity_setpoint_sensor(Sensor *sensor) { this->humidity_sensor_ = sensor; } + void set_power_sensor(Sensor *sensor) { this->power_sensor_ = sensor; } + void on_status_change() override; + + /* ############### */ + /* ### ACTIONS ### */ + /* ############### */ + + void do_follow_me(float temperature, bool beeper = false); + void do_display_toggle(); + void do_swing_step(); + void do_beeper_on() { this->set_beeper_feedback(true); } + void do_beeper_off() { this->set_beeper_feedback(false); } + void do_power_on() { this->base_.setPowerState(true); } + void do_power_off() { this->base_.setPowerState(false); } + + protected: + void control(const ClimateCall &call) override; + ClimateTraits traits() override; + Sensor *outdoor_sensor_{nullptr}; + Sensor *humidity_sensor_{nullptr}; + Sensor *power_sensor_{nullptr}; +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h new file mode 100644 index 0000000000..aa616ced36 --- /dev/null +++ b/esphome/components/midea/appliance_base.h @@ -0,0 +1,76 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/climate/climate.h" +#ifdef USE_REMOTE_TRANSMITTER +#include "esphome/components/remote_base/midea_protocol.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#endif +#include +#include + +namespace esphome { +namespace midea { + +using climate::ClimatePreset; +using climate::ClimateTraits; +using climate::ClimateMode; +using climate::ClimateSwingMode; +using climate::ClimateFanMode; + +template class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate { + static_assert(std::is_base_of::value, + "T must derive from dudanov::midea::ApplianceBase class"); + + public: + ApplianceBase() { + this->base_.setStream(this); + this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); + dudanov::midea::ApplianceBase::setLogger([](int level, const char *tag, int line, String format, va_list args) { + esp_log_vprintf_(level, tag, line, format.c_str(), args); + }); + } + bool can_proceed() override { + return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; + } + float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } + void setup() override { this->base_.setup(); } + void loop() override { this->base_.loop(); } + void set_period(uint32_t ms) { this->base_.setPeriod(ms); } + void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); } + void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } + void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } + void set_autoconf(bool value) { this->base_.setAutoconf(value); } + void set_supported_modes(std::set modes) { this->supported_modes_ = std::move(modes); } + void set_supported_swing_modes(std::set modes) { this->supported_swing_modes_ = std::move(modes); } + void set_supported_presets(std::set presets) { this->supported_presets_ = std::move(presets); } + void set_custom_presets(std::set presets) { this->supported_custom_presets_ = std::move(presets); } + void set_custom_fan_modes(std::set modes) { this->supported_custom_fan_modes_ = std::move(modes); } + virtual void on_status_change() = 0; +#ifdef USE_REMOTE_TRANSMITTER + void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { + this->transmitter_ = transmitter; + } + void transmit_ir(remote_base::MideaData &data) { + data.finalize(); + auto transmit = this->transmitter_->transmit(); + remote_base::MideaProtocol().encode(transmit.get_data(), data); + transmit.perform(); + } +#endif + + protected: + T base_; + std::set supported_modes_{}; + std::set supported_swing_modes_{}; + std::set supported_presets_{}; + std::set supported_custom_presets_{}; + std::set supported_custom_fan_modes_{}; +#ifdef USE_REMOTE_TRANSMITTER + remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; +#endif +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/automations.h new file mode 100644 index 0000000000..1f026c0c15 --- /dev/null +++ b/esphome/components/midea/automations.h @@ -0,0 +1,56 @@ +#pragma once +#include "esphome/core/automation.h" +#include "air_conditioner.h" + +namespace esphome { +namespace midea { + +template class MideaActionBase : public Action { + public: + void set_parent(AirConditioner *parent) { this->parent_ = parent; } + + protected: + AirConditioner *parent_; +}; + +template class FollowMeAction : public MideaActionBase { + TEMPLATABLE_VALUE(float, temperature) + TEMPLATABLE_VALUE(bool, beeper) + + void play(Ts... x) override { + this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...)); + } +}; + +template class SwingStepAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_swing_step(); } +}; + +template class DisplayToggleAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_display_toggle(); } +}; + +template class BeeperOnAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_beeper_on(); } +}; + +template class BeeperOffAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_beeper_off(); } +}; + +template class PowerOnAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_power_on(); } +}; + +template class PowerOffAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_power_off(); } +}; + +} // namespace midea +} // namespace esphome diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py new file mode 100644 index 0000000000..137fcdd607 --- /dev/null +++ b/esphome/components/midea/climate.py @@ -0,0 +1,284 @@ +from esphome.core import coroutine +from esphome import automation +from esphome.components import climate, sensor, uart, remote_transmitter +from esphome.components.remote_base import CONF_TRANSMITTER_ID +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_AUTOCONF, + CONF_BEEPER, + CONF_CUSTOM_FAN_MODES, + CONF_CUSTOM_PRESETS, + CONF_ID, + CONF_NUM_ATTEMPTS, + CONF_PERIOD, + CONF_SUPPORTED_MODES, + CONF_SUPPORTED_PRESETS, + CONF_SUPPORTED_SWING_MODES, + CONF_TIMEOUT, + CONF_TEMPERATURE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + ICON_POWER, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + UNIT_WATT, +) +from esphome.components.climate import ( + ClimateMode, + ClimatePreset, + ClimateSwingMode, +) + +CODEOWNERS = ["@dudanov"] +DEPENDENCIES = ["climate", "uart", "wifi"] +AUTO_LOAD = ["sensor"] +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" +CONF_POWER_USAGE = "power_usage" +CONF_HUMIDITY_SETPOINT = "humidity_setpoint" +midea_ns = cg.esphome_ns.namespace("midea") +AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component) +Capabilities = midea_ns.namespace("Constants") + + +def templatize(value): + if isinstance(value, cv.Schema): + value = value.schema + ret = {} + for key, val in value.items(): + ret[key] = cv.templatable(val) + return cv.Schema(ret) + + +def register_action(name, type_, schema): + validator = templatize(schema).extend(MIDEA_ACTION_BASE_SCHEMA) + registerer = automation.register_action(f"midea_ac.{name}", type_, validator) + + def decorator(func): + async def new_func(config, action_id, template_arg, args): + ac_ = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg) + cg.add(var.set_parent(ac_)) + await coroutine(func)(var, config, args) + return var + + return registerer(new_func) + + return decorator + + +ALLOWED_CLIMATE_MODES = { + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, +} + +ALLOWED_CLIMATE_PRESETS = { + "ECO": ClimatePreset.CLIMATE_PRESET_ECO, + "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, + "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, +} + +ALLOWED_CLIMATE_SWING_MODES = { + "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, + "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, +} + +CUSTOM_FAN_MODES = { + "SILENT": Capabilities.SILENT, + "TURBO": Capabilities.TURBO, +} + +CUSTOM_PRESETS = { + "FREEZE_PROTECTION": Capabilities.FREEZE_PROTECTION, +} + +validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True) +validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True) +validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) +validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) +validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) + +CONFIG_SCHEMA = cv.All( + climate.CLIMATE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirConditioner), + cv.Optional(CONF_PERIOD, default="1s"): cv.time_period, + cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period, + cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5), + cv.Optional(CONF_TRANSMITTER_ID): cv.use_id( + remote_transmitter.RemoteTransmitterComponent + ), + cv.Optional(CONF_BEEPER, default=False): cv.boolean, + cv.Optional(CONF_AUTOCONF, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes), + cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list( + validate_swing_modes + ), + cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets), + cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets), + cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list( + validate_custom_fan_modes + ), + cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_POWER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +# Actions +FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action) +DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action) +SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action) +BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action) +BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action) +PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action) +PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action) + +MIDEA_ACTION_BASE_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(AirConditioner), + } +) + +# FollowMe action +MIDEA_FOLLOW_ME_MIN = 0 +MIDEA_FOLLOW_ME_MAX = 37 +MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( + { + cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), + } +) + + +@register_action("follow_me", FollowMeAction, MIDEA_FOLLOW_ME_SCHEMA) +async def follow_me_to_code(var, config, args): + template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) + cg.add(var.set_beeper(template_)) + template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) + cg.add(var.set_temperature(template_)) + + +# Toggle Display action +@register_action( + "display_toggle", + DisplayToggleAction, + cv.Schema({}), +) +async def display_toggle_to_code(var, config, args): + pass + + +# Swing Step action +@register_action( + "swing_step", + SwingStepAction, + cv.Schema({}), +) +async def swing_step_to_code(var, config, args): + pass + + +# Beeper On action +@register_action( + "beeper_on", + BeeperOnAction, + cv.Schema({}), +) +async def beeper_on_to_code(var, config, args): + pass + + +# Beeper Off action +@register_action( + "beeper_off", + BeeperOffAction, + cv.Schema({}), +) +async def beeper_off_to_code(var, config, args): + pass + + +# Power On action +@register_action( + "power_on", + PowerOnAction, + cv.Schema({}), +) +async def power_on_to_code(var, config, args): + pass + + +# Power Off action +@register_action( + "power_off", + PowerOffAction, + cv.Schema({}), +) +async def power_off_to_code(var, config, args): + pass + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + await climate.register_climate(var, config) + cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) + cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds)) + cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS])) + if CONF_TRANSMITTER_ID in config: + cg.add_define("USE_REMOTE_TRANSMITTER") + transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter_)) + cg.add(var.set_beeper_feedback(config[CONF_BEEPER])) + cg.add(var.set_autoconf(config[CONF_AUTOCONF])) + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) + if CONF_SUPPORTED_SWING_MODES in config: + cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) + if CONF_SUPPORTED_PRESETS in config: + cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) + if CONF_CUSTOM_PRESETS in config: + cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) + if CONF_CUSTOM_FAN_MODES in config: + cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) + if CONF_OUTDOOR_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) + cg.add(var.set_outdoor_temperature_sensor(sens)) + if CONF_POWER_USAGE in config: + sens = await sensor.new_sensor(config[CONF_POWER_USAGE]) + cg.add(var.set_power_sensor(sens)) + if CONF_HUMIDITY_SETPOINT in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) + cg.add(var.set_humidity_setpoint_sensor(sens)) + cg.add_library("dudanov/MideaUART", "1.1.5") diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/midea_ir.h new file mode 100644 index 0000000000..2459d844a1 --- /dev/null +++ b/esphome/components/midea/midea_ir.h @@ -0,0 +1,42 @@ +#pragma once +#ifdef USE_REMOTE_TRANSMITTER +#include "esphome/components/remote_base/midea_protocol.h" + +namespace esphome { +namespace midea { + +using IrData = remote_base::MideaData; + +class IrFollowMeData : public IrData { + public: + // Default constructor (temp: 30C, beeper: off) + IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} + // Copy from Base + IrFollowMeData(const IrData &data) : IrData(data) {} + // Direct from temperature and beeper values + IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { + this->set_temp(temp); + this->set_beeper(beeper); + } + + /* TEMPERATURE */ + uint8_t temp() const { return this->data_[4] - 1; } + void set_temp(uint8_t val) { this->data_[4] = std::min(MAX_TEMP, val) + 1; } + + /* BEEPER */ + bool beeper() const { return this->data_[3] & 128; } + void set_beeper(bool val) { this->set_value_(3, 1, 7, val); } + + protected: + static const uint8_t MAX_TEMP = 37; +}; + +class IrSpecialData : public IrData { + public: + IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} +}; + +} // namespace midea +} // namespace esphome + +#endif diff --git a/esphome/components/midea_ac/climate.py b/esphome/components/midea_ac/climate.py index 741741fd03..f336f84787 100644 --- a/esphome/components/midea_ac/climate.py +++ b/esphome/components/midea_ac/climate.py @@ -1,115 +1,3 @@ -from esphome.components import climate, sensor import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.const import ( - CONF_CUSTOM_FAN_MODES, - CONF_CUSTOM_PRESETS, - CONF_ID, - CONF_PRESET_BOOST, - CONF_PRESET_ECO, - CONF_PRESET_SLEEP, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, - UNIT_PERCENT, - UNIT_WATT, - ICON_THERMOMETER, - ICON_POWER, - DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, - ICON_WATER_PERCENT, - DEVICE_CLASS_HUMIDITY, -) -from esphome.components.midea_dongle import CONF_MIDEA_DONGLE_ID, MideaDongle -AUTO_LOAD = ["climate", "sensor", "midea_dongle"] -CODEOWNERS = ["@dudanov"] -CONF_BEEPER = "beeper" -CONF_SWING_HORIZONTAL = "swing_horizontal" -CONF_SWING_BOTH = "swing_both" -CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" -CONF_POWER_USAGE = "power_usage" -CONF_HUMIDITY_SETPOINT = "humidity_setpoint" -midea_ac_ns = cg.esphome_ns.namespace("midea_ac") -MideaAC = midea_ac_ns.class_("MideaAC", climate.Climate, cg.Component) - -CLIMATE_CUSTOM_FAN_MODES = { - "SILENT": "silent", - "TURBO": "turbo", -} - -validate_climate_custom_fan_mode = cv.enum(CLIMATE_CUSTOM_FAN_MODES, upper=True) - -CLIMATE_CUSTOM_PRESETS = { - "FREEZE_PROTECTION": "freeze protection", -} - -validate_climate_custom_preset = cv.enum(CLIMATE_CUSTOM_PRESETS, upper=True) - -CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MideaAC), - cv.GenerateID(CONF_MIDEA_DONGLE_ID): cv.use_id(MideaDongle), - cv.Optional(CONF_BEEPER, default=False): cv.boolean, - cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list( - validate_climate_custom_fan_mode - ), - cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list( - validate_climate_custom_preset - ), - cv.Optional(CONF_SWING_HORIZONTAL, default=False): cv.boolean, - cv.Optional(CONF_SWING_BOTH, default=False): cv.boolean, - cv.Optional(CONF_PRESET_ECO, default=False): cv.boolean, - cv.Optional(CONF_PRESET_SLEEP, default=False): cv.boolean, - cv.Optional(CONF_PRESET_BOOST, default=False): cv.boolean, - cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - icon=ICON_POWER, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - icon=ICON_WATER_PERCENT, - accuracy_decimals=0, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ).extend(cv.COMPONENT_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await climate.register_climate(var, config) - paren = await cg.get_variable(config[CONF_MIDEA_DONGLE_ID]) - cg.add(var.set_midea_dongle_parent(paren)) - cg.add(var.set_beeper_feedback(config[CONF_BEEPER])) - if CONF_CUSTOM_FAN_MODES in config: - cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - if CONF_CUSTOM_PRESETS in config: - cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS])) - cg.add(var.set_swing_horizontal(config[CONF_SWING_HORIZONTAL])) - cg.add(var.set_swing_both(config[CONF_SWING_BOTH])) - cg.add(var.set_preset_eco(config[CONF_PRESET_ECO])) - cg.add(var.set_preset_sleep(config[CONF_PRESET_SLEEP])) - cg.add(var.set_preset_boost(config[CONF_PRESET_BOOST])) - if CONF_OUTDOOR_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) - cg.add(var.set_outdoor_temperature_sensor(sens)) - if CONF_POWER_USAGE in config: - sens = await sensor.new_sensor(config[CONF_POWER_USAGE]) - cg.add(var.set_power_sensor(sens)) - if CONF_HUMIDITY_SETPOINT in config: - sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT]) - cg.add(var.set_humidity_setpoint_sensor(sens)) +CONFIG_SCHEMA = cv.invalid("This platform has been renamed to midea in 2021.9") diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp deleted file mode 100644 index 72f7d23404..0000000000 --- a/esphome/components/midea_ac/midea_climate.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "esphome/core/log.h" -#include "midea_climate.h" - -namespace esphome { -namespace midea_ac { - -static const char *const TAG = "midea_ac"; - -static void set_sensor(sensor::Sensor *sensor, float value) { - if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) - sensor->publish_state(value); -} - -template void set_property(T &property, T value, bool &flag) { - if (property != value) { - property = value; - flag = true; - } -} - -void MideaAC::on_frame(const midea_dongle::Frame &frame) { - const auto p = frame.as(); - if (p.has_power_info()) { - set_sensor(this->power_sensor_, p.get_power_usage()); - return; - } else if (!p.has_properties()) { - ESP_LOGW(TAG, "RX: frame has unknown type"); - return; - } - if (p.get_type() == midea_dongle::MideaMessageType::DEVICE_CONTROL) { - ESP_LOGD(TAG, "RX: control frame"); - this->ctrl_request_ = false; - } else { - ESP_LOGD(TAG, "RX: query frame"); - } - if (this->ctrl_request_) - return; - this->cmd_frame_.set_properties(p); // copy properties from response - bool need_publish = false; - set_property(this->mode, p.get_mode(), need_publish); - set_property(this->target_temperature, p.get_target_temp(), need_publish); - set_property(this->current_temperature, p.get_indoor_temp(), need_publish); - if (p.is_custom_fan_mode()) { - this->fan_mode.reset(); - optional mode = p.get_custom_fan_mode(); - set_property(this->custom_fan_mode, mode, need_publish); - } else { - this->custom_fan_mode.reset(); - optional mode = p.get_fan_mode(); - set_property(this->fan_mode, mode, need_publish); - } - set_property(this->swing_mode, p.get_swing_mode(), need_publish); - if (p.is_custom_preset()) { - this->preset.reset(); - optional preset = p.get_custom_preset(); - set_property(this->custom_preset, preset, need_publish); - } else { - this->custom_preset.reset(); - set_property(this->preset, p.get_preset(), need_publish); - } - if (need_publish) - this->publish_state(); - set_sensor(this->outdoor_sensor_, p.get_outdoor_temp()); - set_sensor(this->humidity_sensor_, p.get_humidity_setpoint()); -} - -void MideaAC::on_update() { - if (this->ctrl_request_) { - ESP_LOGD(TAG, "TX: control"); - this->parent_->write_frame(this->cmd_frame_); - } else { - ESP_LOGD(TAG, "TX: query"); - if (this->power_sensor_ == nullptr || this->request_num_++ % 32) - this->parent_->write_frame(this->query_frame_); - else - this->parent_->write_frame(this->power_frame_); - } -} - -bool MideaAC::allow_preset(climate::ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - if (this->mode == climate::CLIMATE_MODE_COOL) { - return true; - } else { - ESP_LOGD(TAG, "ECO preset is only available in COOL mode"); - } - break; - case climate::CLIMATE_PRESET_SLEEP: - if (this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_DRY) { - ESP_LOGD(TAG, "SLEEP preset is not available in FAN_ONLY or DRY mode"); - } else { - return true; - } - break; - case climate::CLIMATE_PRESET_BOOST: - if (this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_COOL) { - return true; - } else { - ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); - } - break; - case climate::CLIMATE_PRESET_NONE: - return true; - default: - break; - } - return false; -} - -bool MideaAC::allow_custom_preset(const std::string &custom_preset) const { - if (custom_preset == MIDEA_FREEZE_PROTECTION_PRESET) { - if (this->mode == climate::CLIMATE_MODE_HEAT) { - return true; - } else { - ESP_LOGD(TAG, "%s is only available in HEAT mode", MIDEA_FREEZE_PROTECTION_PRESET.c_str()); - } - } - return false; -} - -void MideaAC::control(const climate::ClimateCall &call) { - if (call.get_mode().has_value() && call.get_mode().value() != this->mode) { - this->cmd_frame_.set_mode(call.get_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_target_temperature().has_value() && call.get_target_temperature().value() != this->target_temperature) { - this->cmd_frame_.set_target_temp(call.get_target_temperature().value()); - this->ctrl_request_ = true; - } - if (call.get_fan_mode().has_value() && - (!this->fan_mode.has_value() || this->fan_mode.value() != call.get_fan_mode().value())) { - this->custom_fan_mode.reset(); - this->cmd_frame_.set_fan_mode(call.get_fan_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_custom_fan_mode().has_value() && - (!this->custom_fan_mode.has_value() || this->custom_fan_mode.value() != call.get_custom_fan_mode().value())) { - this->fan_mode.reset(); - this->cmd_frame_.set_custom_fan_mode(call.get_custom_fan_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_swing_mode().has_value() && call.get_swing_mode().value() != this->swing_mode) { - this->cmd_frame_.set_swing_mode(call.get_swing_mode().value()); - this->ctrl_request_ = true; - } - if (call.get_preset().has_value() && this->allow_preset(call.get_preset().value()) && - (!this->preset.has_value() || this->preset.value() != call.get_preset().value())) { - this->custom_preset.reset(); - this->cmd_frame_.set_preset(call.get_preset().value()); - this->ctrl_request_ = true; - } - if (call.get_custom_preset().has_value() && this->allow_custom_preset(call.get_custom_preset().value()) && - (!this->custom_preset.has_value() || this->custom_preset.value() != call.get_custom_preset().value())) { - this->preset.reset(); - this->cmd_frame_.set_custom_preset(call.get_custom_preset().value()); - this->ctrl_request_ = true; - } - if (this->ctrl_request_) { - this->cmd_frame_.set_beeper_feedback(this->beeper_feedback_); - this->cmd_frame_.finalize(); - } -} - -climate::ClimateTraits MideaAC::traits() { - auto traits = climate::ClimateTraits(); - traits.set_visual_min_temperature(17); - traits.set_visual_max_temperature(30); - traits.set_visual_temperature_step(0.5); - traits.set_supported_modes({ - climate::CLIMATE_MODE_OFF, - climate::CLIMATE_MODE_HEAT_COOL, - climate::CLIMATE_MODE_COOL, - climate::CLIMATE_MODE_DRY, - climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_FAN_ONLY, - }); - traits.set_supported_fan_modes({ - climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, - climate::CLIMATE_FAN_HIGH, - }); - traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supported_swing_modes({ - climate::CLIMATE_SWING_OFF, - climate::CLIMATE_SWING_VERTICAL, - }); - if (traits_swing_horizontal_) - traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); - if (traits_swing_both_) - traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); - traits.set_supported_presets({ - climate::CLIMATE_PRESET_NONE, - }); - if (traits_preset_eco_) - traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); - if (traits_preset_sleep_) - traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); - if (traits_preset_boost_) - traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); - traits.set_supported_custom_presets(this->traits_custom_presets_); - traits.set_supports_current_temperature(true); - return traits; -} - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h deleted file mode 100644 index 62bd4c339e..0000000000 --- a/esphome/components/midea_ac/midea_climate.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/midea_dongle/midea_dongle.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/midea_dongle/midea_dongle.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/core/component.h" -#include "midea_frame.h" - -namespace esphome { -namespace midea_ac { - -class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, public Component { - public: - float get_setup_priority() const override { return setup_priority::LATE; } - void on_frame(const midea_dongle::Frame &frame) override; - void on_update() override; - void setup() override { this->parent_->set_appliance(this); } - void set_midea_dongle_parent(midea_dongle::MideaDongle *parent) { this->parent_ = parent; } - void set_outdoor_temperature_sensor(sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } - void set_humidity_setpoint_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; } - void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } - void set_beeper_feedback(bool state) { this->beeper_feedback_ = state; } - void set_swing_horizontal(bool state) { this->traits_swing_horizontal_ = state; } - void set_swing_both(bool state) { this->traits_swing_both_ = state; } - void set_preset_eco(bool state) { this->traits_preset_eco_ = state; } - void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } - void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } - bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::set custom_fan_modes) { - this->traits_custom_fan_modes_ = std::move(custom_fan_modes); - } - void set_custom_presets(std::set custom_presets) { - this->traits_custom_presets_ = std::move(custom_presets); - } - bool allow_custom_preset(const std::string &custom_preset) const; - - protected: - /// Override control to change settings of the climate device. - void control(const climate::ClimateCall &call) override; - /// Return the traits of this controller. - climate::ClimateTraits traits() override; - - const QueryFrame query_frame_; - const PowerQueryFrame power_frame_; - CommandFrame cmd_frame_; - midea_dongle::MideaDongle *parent_{nullptr}; - sensor::Sensor *outdoor_sensor_{nullptr}; - sensor::Sensor *humidity_sensor_{nullptr}; - sensor::Sensor *power_sensor_{nullptr}; - uint8_t request_num_{0}; - bool ctrl_request_{false}; - bool beeper_feedback_{false}; - bool traits_swing_horizontal_{false}; - bool traits_swing_both_{false}; - bool traits_preset_eco_{false}; - bool traits_preset_sleep_{false}; - bool traits_preset_boost_{false}; - std::set traits_custom_fan_modes_{{}}; - std::set traits_custom_presets_{{}}; -}; - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp deleted file mode 100644 index c0a5ce4b55..0000000000 --- a/esphome/components/midea_ac/midea_frame.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "midea_frame.h" - -namespace esphome { -namespace midea_ac { - -static const char *const TAG = "midea_ac"; -const std::string MIDEA_SILENT_FAN_MODE = "silent"; -const std::string MIDEA_TURBO_FAN_MODE = "turbo"; -const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; - -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, - 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; - -const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, - 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x17, 0x6A}; - -const uint8_t CommandFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x40, 0x00, - 0x00, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -float PropertiesFrame::get_target_temp() const { - float temp = static_cast((this->pbuf_[12] & 0x0F) + 16); - if (this->pbuf_[12] & 0x10) - temp += 0.5; - return temp; -} - -void PropertiesFrame::set_target_temp(float temp) { - uint8_t tmp = static_cast(temp * 16.0) + 4; - tmp = ((tmp & 8) << 1) | (tmp >> 4); - this->pbuf_[12] &= ~0x1F; - this->pbuf_[12] |= tmp; -} - -static float i16tof(int16_t in) { return static_cast(in - 50) / 2.0; } -float PropertiesFrame::get_indoor_temp() const { return i16tof(this->pbuf_[21]); } -float PropertiesFrame::get_outdoor_temp() const { return i16tof(this->pbuf_[22]); } -float PropertiesFrame::get_humidity_setpoint() const { return static_cast(this->pbuf_[29] & 0x7F); } - -climate::ClimateMode PropertiesFrame::get_mode() const { - if (!this->get_power_()) - return climate::CLIMATE_MODE_OFF; - switch (this->pbuf_[12] >> 5) { - case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_HEAT_COOL; - case MIDEA_MODE_COOL: - return climate::CLIMATE_MODE_COOL; - case MIDEA_MODE_DRY: - return climate::CLIMATE_MODE_DRY; - case MIDEA_MODE_HEAT: - return climate::CLIMATE_MODE_HEAT; - case MIDEA_MODE_FAN_ONLY: - return climate::CLIMATE_MODE_FAN_ONLY; - default: - return climate::CLIMATE_MODE_OFF; - } -} - -void PropertiesFrame::set_mode(climate::ClimateMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_MODE_HEAT_COOL: - m = MIDEA_MODE_AUTO; - break; - case climate::CLIMATE_MODE_COOL: - m = MIDEA_MODE_COOL; - break; - case climate::CLIMATE_MODE_DRY: - m = MIDEA_MODE_DRY; - break; - case climate::CLIMATE_MODE_HEAT: - m = MIDEA_MODE_HEAT; - break; - case climate::CLIMATE_MODE_FAN_ONLY: - m = MIDEA_MODE_FAN_ONLY; - break; - default: - this->set_power_(false); - return; - } - this->set_power_(true); - this->pbuf_[12] &= ~0xE0; - this->pbuf_[12] |= m << 5; -} - -optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) - return climate::CLIMATE_PRESET_ECO; - if (this->get_sleep_mode()) - return climate::CLIMATE_PRESET_SLEEP; - if (this->get_turbo_mode()) - return climate::CLIMATE_PRESET_BOOST; - return climate::CLIMATE_PRESET_NONE; -} - -void PropertiesFrame::set_preset(climate::ClimatePreset preset) { - this->clear_presets(); - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - this->set_eco_mode(true); - break; - case climate::CLIMATE_PRESET_SLEEP: - this->set_sleep_mode(true); - break; - case climate::CLIMATE_PRESET_BOOST: - this->set_turbo_mode(true); - break; - default: - break; - } -} - -void PropertiesFrame::clear_presets() { - this->set_eco_mode(false); - this->set_sleep_mode(false); - this->set_turbo_mode(false); - this->set_freeze_protection_mode(false); -} - -bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } - -const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; - -void PropertiesFrame::set_custom_preset(const std::string &preset) { - this->clear_presets(); - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) - this->set_freeze_protection_mode(true); -} - -bool PropertiesFrame::is_custom_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_SILENT: - case MIDEA_FAN_TURBO: - return true; - default: - return false; - } -} - -climate::ClimateFanMode PropertiesFrame::get_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_LOW: - return climate::CLIMATE_FAN_LOW; - case MIDEA_FAN_MEDIUM: - return climate::CLIMATE_FAN_MEDIUM; - case MIDEA_FAN_HIGH: - return climate::CLIMATE_FAN_HIGH; - default: - return climate::CLIMATE_FAN_AUTO; - } -} - -void PropertiesFrame::set_fan_mode(climate::ClimateFanMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_FAN_LOW: - m = MIDEA_FAN_LOW; - break; - case climate::CLIMATE_FAN_MEDIUM: - m = MIDEA_FAN_MEDIUM; - break; - case climate::CLIMATE_FAN_HIGH: - m = MIDEA_FAN_HIGH; - break; - default: - m = MIDEA_FAN_AUTO; - break; - } - this->pbuf_[13] = m; -} - -const std::string &PropertiesFrame::get_custom_fan_mode() const { - switch (this->pbuf_[13]) { - case MIDEA_FAN_SILENT: - return MIDEA_SILENT_FAN_MODE; - default: - return MIDEA_TURBO_FAN_MODE; - } -} - -void PropertiesFrame::set_custom_fan_mode(const std::string &mode) { - uint8_t m; - if (mode == MIDEA_SILENT_FAN_MODE) { - m = MIDEA_FAN_SILENT; - } else { - m = MIDEA_FAN_TURBO; - } - this->pbuf_[13] = m; -} - -climate::ClimateSwingMode PropertiesFrame::get_swing_mode() const { - switch (this->pbuf_[17] & 0x0F) { - case MIDEA_SWING_VERTICAL: - return climate::CLIMATE_SWING_VERTICAL; - case MIDEA_SWING_HORIZONTAL: - return climate::CLIMATE_SWING_HORIZONTAL; - case MIDEA_SWING_BOTH: - return climate::CLIMATE_SWING_BOTH; - default: - return climate::CLIMATE_SWING_OFF; - } -} - -void PropertiesFrame::set_swing_mode(climate::ClimateSwingMode mode) { - uint8_t m; - switch (mode) { - case climate::CLIMATE_SWING_VERTICAL: - m = MIDEA_SWING_VERTICAL; - break; - case climate::CLIMATE_SWING_HORIZONTAL: - m = MIDEA_SWING_HORIZONTAL; - break; - case climate::CLIMATE_SWING_BOTH: - m = MIDEA_SWING_BOTH; - break; - default: - m = MIDEA_SWING_OFF; - break; - } - this->pbuf_[17] = 0x30 | m; -} - -float PropertiesFrame::get_power_usage() const { - uint32_t power = 0; - const uint8_t *ptr = this->pbuf_ + 28; - for (uint32_t weight = 1;; weight *= 10, ptr--) { - power += (*ptr % 16) * weight; - weight *= 10; - power += (*ptr / 16) * weight; - if (weight == 100000) - return static_cast(power) * 0.1; - } -} - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h deleted file mode 100644 index e1d6fed49d..0000000000 --- a/esphome/components/midea_ac/midea_frame.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once -#include "esphome/components/climate/climate.h" -#include "esphome/components/midea_dongle/midea_frame.h" - -namespace esphome { -namespace midea_ac { - -extern const std::string MIDEA_SILENT_FAN_MODE; -extern const std::string MIDEA_TURBO_FAN_MODE; -extern const std::string MIDEA_FREEZE_PROTECTION_PRESET; - -/// Enum for all modes a Midea device can be in. -enum MideaMode : uint8_t { - /// The Midea device is set to automatically change the heating/cooling cycle - MIDEA_MODE_AUTO = 1, - /// The Midea device is manually set to cool mode (not in auto mode!) - MIDEA_MODE_COOL = 2, - /// The Midea device is manually set to dry mode - MIDEA_MODE_DRY = 3, - /// The Midea device is manually set to heat mode (not in auto mode!) - MIDEA_MODE_HEAT = 4, - /// The Midea device is manually set to fan only mode - MIDEA_MODE_FAN_ONLY = 5, -}; - -/// Enum for all modes a Midea fan can be in -enum MideaFanMode : uint8_t { - /// The fan mode is set to Auto - MIDEA_FAN_AUTO = 102, - /// The fan mode is set to Silent - MIDEA_FAN_SILENT = 20, - /// The fan mode is set to Low - MIDEA_FAN_LOW = 40, - /// The fan mode is set to Medium - MIDEA_FAN_MEDIUM = 60, - /// The fan mode is set to High - MIDEA_FAN_HIGH = 80, - /// The fan mode is set to Turbo - MIDEA_FAN_TURBO = 100, -}; - -/// Enum for all modes a Midea swing can be in -enum MideaSwingMode : uint8_t { - /// The sing mode is set to Off - MIDEA_SWING_OFF = 0b0000, - /// The fan mode is set to Both - MIDEA_SWING_BOTH = 0b1111, - /// The fan mode is set to Vertical - MIDEA_SWING_VERTICAL = 0b1100, - /// The fan mode is set to Horizontal - MIDEA_SWING_HORIZONTAL = 0b0011, -}; - -class PropertiesFrame : public midea_dongle::BaseFrame { - public: - PropertiesFrame() = delete; - PropertiesFrame(uint8_t *data) : BaseFrame(data) {} - PropertiesFrame(const Frame &frame) : BaseFrame(frame) {} - - bool has_properties() const { - return this->has_response_type(0xC0) && (this->has_type(0x03) || this->has_type(0x02)); - } - - bool has_power_info() const { return this->has_response_type(0xC1); } - - /* TARGET TEMPERATURE */ - - float get_target_temp() const; - void set_target_temp(float temp); - - /* MODE */ - climate::ClimateMode get_mode() const; - void set_mode(climate::ClimateMode mode); - - /* FAN SPEED */ - bool is_custom_fan_mode() const; - climate::ClimateFanMode get_fan_mode() const; - void set_fan_mode(climate::ClimateFanMode mode); - - const std::string &get_custom_fan_mode() const; - void set_custom_fan_mode(const std::string &mode); - - /* SWING MODE */ - climate::ClimateSwingMode get_swing_mode() const; - void set_swing_mode(climate::ClimateSwingMode mode); - - /* INDOOR TEMPERATURE */ - float get_indoor_temp() const; - - /* OUTDOOR TEMPERATURE */ - float get_outdoor_temp() const; - - /* HUMIDITY SETPOINT */ - float get_humidity_setpoint() const; - - /* ECO MODE */ - bool get_eco_mode() const { return this->pbuf_[19] & 0x10; } - void set_eco_mode(bool state) { this->set_bytemask_(19, 0x80, state); } - - /* SLEEP MODE */ - bool get_sleep_mode() const { return this->pbuf_[20] & 0x01; } - void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } - - /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } - void set_turbo_mode(bool state) { - this->set_bytemask_(18, 0x20, state); - this->set_bytemask_(20, 0x02, state); - } - - /* FREEZE PROTECTION */ - bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } - void set_freeze_protection_mode(bool state) { this->set_bytemask_(31, 0x80, state); } - - /* PRESET */ - optional get_preset() const; - void set_preset(climate::ClimatePreset preset); - void clear_presets(); - - bool is_custom_preset() const; - const std::string &get_custom_preset() const; - void set_custom_preset(const std::string &preset); - - /* POWER USAGE */ - float get_power_usage() const; - - /// Set properties from another frame - void set_properties(const PropertiesFrame &p) { memcpy(this->pbuf_ + 11, p.data() + 11, 10); } - - protected: - /* POWER */ - bool get_power_() const { return this->pbuf_[11] & 0x01; } - void set_power_(bool state) { this->set_bytemask_(11, 0x01, state); } -}; - -// Query state frame (read-only) -class QueryFrame : public midea_dongle::StaticFrame { - public: - QueryFrame() : StaticFrame(FPSTR(this->INIT)) {} - - private: - static const uint8_t PROGMEM INIT[]; -}; - -// Power query state frame (read-only) -class PowerQueryFrame : public midea_dongle::StaticFrame { - public: - PowerQueryFrame() : StaticFrame(FPSTR(this->INIT)) {} - - private: - static const uint8_t PROGMEM INIT[]; -}; - -// Command frame -class CommandFrame : public midea_dongle::StaticFrame { - public: - CommandFrame() : StaticFrame(FPSTR(this->INIT)) {} - void set_beeper_feedback(bool state) { this->set_bytemask_(11, 0x40, state); } - - private: - static const uint8_t PROGMEM INIT[]; -}; - -} // namespace midea_ac -} // namespace esphome diff --git a/esphome/components/midea_dongle/__init__.py b/esphome/components/midea_dongle/__init__.py deleted file mode 100644 index daa8ea6657..0000000000 --- a/esphome/components/midea_dongle/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import uart -from esphome.const import CONF_ID - -DEPENDENCIES = ["wifi", "uart"] -CODEOWNERS = ["@dudanov"] - -midea_dongle_ns = cg.esphome_ns.namespace("midea_dongle") -MideaDongle = midea_dongle_ns.class_("MideaDongle", cg.Component, uart.UARTDevice) - -CONF_MIDEA_DONGLE_ID = "midea_dongle_id" -CONF_STRENGTH_ICON = "strength_icon" -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(MideaDongle), - cv.Optional(CONF_STRENGTH_ICON, default=False): cv.boolean, - } - ) - .extend(cv.COMPONENT_SCHEMA) - .extend(uart.UART_DEVICE_SCHEMA) -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - await uart.register_uart_device(var, config) - cg.add(var.use_strength_icon(config[CONF_STRENGTH_ICON])) diff --git a/esphome/components/midea_dongle/midea_dongle.cpp b/esphome/components/midea_dongle/midea_dongle.cpp deleted file mode 100644 index 7e3683a964..0000000000 --- a/esphome/components/midea_dongle/midea_dongle.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "midea_dongle.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" - -namespace esphome { -namespace midea_dongle { - -static const char *const TAG = "midea_dongle"; - -void MideaDongle::loop() { - while (this->available()) { - const uint8_t rx = this->read(); - if (this->idx_ <= OFFSET_LENGTH) { - if (this->idx_ == OFFSET_LENGTH) { - if (rx <= OFFSET_BODY || rx >= sizeof(this->buf_)) { - this->reset_(); - continue; - } - this->cnt_ = rx; - } else if (rx != SYNC_BYTE) { - continue; - } - } - this->buf_[this->idx_++] = rx; - if (--this->cnt_) - continue; - this->reset_(); - const BaseFrame frame(this->buf_); - ESP_LOGD(TAG, "RX: %s", frame.to_string().c_str()); - if (!frame.is_valid()) { - ESP_LOGW(TAG, "RX: frame check failed!"); - continue; - } - if (frame.get_type() == QUERY_NETWORK) { - this->notify_.set_type(QUERY_NETWORK); - this->need_notify_ = true; - continue; - } - if (this->appliance_ != nullptr) - this->appliance_->on_frame(frame); - } -} - -void MideaDongle::update() { - const bool is_conn = WiFi.isConnected(); - uint8_t wifi_strength = 0; - if (!this->rssi_timer_) { - if (is_conn) - wifi_strength = 4; - } else if (is_conn) { - if (--this->rssi_timer_) { - wifi_strength = this->notify_.get_signal_strength(); - } else { - this->rssi_timer_ = 60; - const int32_t dbm = WiFi.RSSI(); - if (dbm > -63) - wifi_strength = 4; - else if (dbm > -75) - wifi_strength = 3; - else if (dbm > -88) - wifi_strength = 2; - else if (dbm > -100) - wifi_strength = 1; - } - } else { - this->rssi_timer_ = 1; - } - if (this->notify_.is_connected() != is_conn) { - this->notify_.set_connected(is_conn); - this->need_notify_ = true; - } - if (this->notify_.get_signal_strength() != wifi_strength) { - this->notify_.set_signal_strength(wifi_strength); - this->need_notify_ = true; - } - if (!--this->notify_timer_) { - this->notify_.set_type(NETWORK_NOTIFY); - this->need_notify_ = true; - } - if (this->need_notify_) { - ESP_LOGD(TAG, "TX: notify WiFi STA %s, signal strength %d", is_conn ? "connected" : "not connected", wifi_strength); - this->need_notify_ = false; - this->notify_timer_ = 600; - this->notify_.finalize(); - this->write_frame(this->notify_); - return; - } - if (this->appliance_ != nullptr) - this->appliance_->on_update(); -} - -void MideaDongle::write_frame(const Frame &frame) { - this->write_array(frame.data(), frame.size()); - ESP_LOGD(TAG, "TX: %s", frame.to_string().c_str()); -} - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_dongle.h b/esphome/components/midea_dongle/midea_dongle.h deleted file mode 100644 index a7dfb9cf25..0000000000 --- a/esphome/components/midea_dongle/midea_dongle.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once -#include "esphome/core/component.h" -#include "esphome/components/wifi/wifi_component.h" -#include "esphome/components/uart/uart.h" -#include "midea_frame.h" - -namespace esphome { -namespace midea_dongle { - -enum MideaApplianceType : uint8_t { DEHUMIDIFIER = 0xA1, AIR_CONDITIONER = 0xAC, BROADCAST = 0xFF }; -enum MideaMessageType : uint8_t { - DEVICE_CONTROL = 0x02, - DEVICE_QUERY = 0x03, - NETWORK_NOTIFY = 0x0D, - QUERY_NETWORK = 0x63, -}; - -struct MideaAppliance { - /// Calling on update event - virtual void on_update() = 0; - /// Calling on frame receive event - virtual void on_frame(const Frame &frame) = 0; -}; - -class MideaDongle : public PollingComponent, public uart::UARTDevice { - public: - MideaDongle() : PollingComponent(1000) {} - float get_setup_priority() const override { return setup_priority::LATE; } - void update() override; - void loop() override; - void set_appliance(MideaAppliance *app) { this->appliance_ = app; } - void use_strength_icon(bool state) { this->rssi_timer_ = state; } - void write_frame(const Frame &frame); - - protected: - MideaAppliance *appliance_{nullptr}; - NotifyFrame notify_; - unsigned notify_timer_{1}; - // Buffer - uint8_t buf_[36]; - // Index - uint8_t idx_{0}; - // Reverse receive counter - uint8_t cnt_{2}; - uint8_t rssi_timer_{0}; - bool need_notify_{false}; - - // Reset receiver state - void reset_() { - this->idx_ = 0; - this->cnt_ = 2; - } -}; - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.cpp b/esphome/components/midea_dongle/midea_frame.cpp deleted file mode 100644 index acb3feee5f..0000000000 --- a/esphome/components/midea_dongle/midea_frame.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "midea_frame.h" - -namespace esphome { -namespace midea_dongle { - -const uint8_t BaseFrame::CRC_TABLE[] = { - 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, 0x9D, 0xC3, 0x21, - 0x7F, 0xFC, 0xA2, 0x40, 0x1E, 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC, 0x23, 0x7D, 0x9F, 0xC1, 0x42, 0x1C, - 0xFE, 0xA0, 0xE1, 0xBF, 0x5D, 0x03, 0x80, 0xDE, 0x3C, 0x62, 0xBE, 0xE0, 0x02, 0x5C, 0xDF, 0x81, 0x63, 0x3D, 0x7C, - 0x22, 0xC0, 0x9E, 0x1D, 0x43, 0xA1, 0xFF, 0x46, 0x18, 0xFA, 0xA4, 0x27, 0x79, 0x9B, 0xC5, 0x84, 0xDA, 0x38, 0x66, - 0xE5, 0xBB, 0x59, 0x07, 0xDB, 0x85, 0x67, 0x39, 0xBA, 0xE4, 0x06, 0x58, 0x19, 0x47, 0xA5, 0xFB, 0x78, 0x26, 0xC4, - 0x9A, 0x65, 0x3B, 0xD9, 0x87, 0x04, 0x5A, 0xB8, 0xE6, 0xA7, 0xF9, 0x1B, 0x45, 0xC6, 0x98, 0x7A, 0x24, 0xF8, 0xA6, - 0x44, 0x1A, 0x99, 0xC7, 0x25, 0x7B, 0x3A, 0x64, 0x86, 0xD8, 0x5B, 0x05, 0xE7, 0xB9, 0x8C, 0xD2, 0x30, 0x6E, 0xED, - 0xB3, 0x51, 0x0F, 0x4E, 0x10, 0xF2, 0xAC, 0x2F, 0x71, 0x93, 0xCD, 0x11, 0x4F, 0xAD, 0xF3, 0x70, 0x2E, 0xCC, 0x92, - 0xD3, 0x8D, 0x6F, 0x31, 0xB2, 0xEC, 0x0E, 0x50, 0xAF, 0xF1, 0x13, 0x4D, 0xCE, 0x90, 0x72, 0x2C, 0x6D, 0x33, 0xD1, - 0x8F, 0x0C, 0x52, 0xB0, 0xEE, 0x32, 0x6C, 0x8E, 0xD0, 0x53, 0x0D, 0xEF, 0xB1, 0xF0, 0xAE, 0x4C, 0x12, 0x91, 0xCF, - 0x2D, 0x73, 0xCA, 0x94, 0x76, 0x28, 0xAB, 0xF5, 0x17, 0x49, 0x08, 0x56, 0xB4, 0xEA, 0x69, 0x37, 0xD5, 0x8B, 0x57, - 0x09, 0xEB, 0xB5, 0x36, 0x68, 0x8A, 0xD4, 0x95, 0xCB, 0x29, 0x77, 0xF4, 0xAA, 0x48, 0x16, 0xE9, 0xB7, 0x55, 0x0B, - 0x88, 0xD6, 0x34, 0x6A, 0x2B, 0x75, 0x97, 0xC9, 0x4A, 0x14, 0xF6, 0xA8, 0x74, 0x2A, 0xC8, 0x96, 0x15, 0x4B, 0xA9, - 0xF7, 0xB6, 0xE8, 0x0A, 0x54, 0xD7, 0x89, 0x6B, 0x35}; - -const uint8_t NotifyFrame::INIT[] = {0xAA, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0D, 0x01, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -bool BaseFrame::is_valid() const { return /*this->has_valid_crc_() &&*/ this->has_valid_cs_(); } - -void BaseFrame::finalize() { - this->update_crc_(); - this->update_cs_(); -} - -void BaseFrame::update_crc_() { - uint8_t crc = 0; - uint8_t *ptr = this->pbuf_ + OFFSET_BODY; - uint8_t len = this->length_() - OFFSET_BODY; - while (--len) - crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr++)); - *ptr = crc; -} - -void BaseFrame::update_cs_() { - uint8_t cs = 0; - uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; - uint8_t len = this->length_(); - while (--len) - cs -= *ptr++; - *ptr = cs; -} - -bool BaseFrame::has_valid_crc_() const { - uint8_t crc = 0; - uint8_t len = this->length_() - OFFSET_BODY; - const uint8_t *ptr = this->pbuf_ + OFFSET_BODY; - for (; len; ptr++, len--) - crc = pgm_read_byte(BaseFrame::CRC_TABLE + (crc ^ *ptr)); - return !crc; -} - -bool BaseFrame::has_valid_cs_() const { - uint8_t cs = 0; - uint8_t len = this->length_(); - const uint8_t *ptr = this->pbuf_ + OFFSET_LENGTH; - for (; len; ptr++, len--) - cs -= *ptr; - return !cs; -} - -void BaseFrame::set_bytemask_(uint8_t idx, uint8_t mask, bool state) { - uint8_t *dst = this->pbuf_ + idx; - if (state) - *dst |= mask; - else - *dst &= ~mask; -} - -static char u4hex(uint8_t num) { return num + ((num < 10) ? '0' : ('A' - 10)); } - -String Frame::to_string() const { - String ret; - char buf[4]; - buf[2] = ' '; - buf[3] = '\0'; - ret.reserve(3 * 36); - const uint8_t *it = this->data(); - for (size_t i = 0; i < this->size(); i++, it++) { - buf[0] = u4hex(*it >> 4); - buf[1] = u4hex(*it & 15); - ret.concat(buf); - } - return ret; -} - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/midea_dongle/midea_frame.h b/esphome/components/midea_dongle/midea_frame.h deleted file mode 100644 index ce89cc636e..0000000000 --- a/esphome/components/midea_dongle/midea_frame.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include "esphome/core/component.h" - -namespace esphome { -namespace midea_dongle { - -static const uint8_t OFFSET_START = 0; -static const uint8_t OFFSET_LENGTH = 1; -static const uint8_t OFFSET_APPTYPE = 2; -static const uint8_t OFFSET_BODY = 10; -static const uint8_t SYNC_BYTE = 0xAA; - -class Frame { - public: - Frame() = delete; - Frame(uint8_t *data) : pbuf_(data) {} - Frame(const Frame &frame) : pbuf_(frame.data()) {} - - // Frame buffer - uint8_t *data() const { return this->pbuf_; } - // Frame size - uint8_t size() const { return this->length_() + OFFSET_LENGTH; } - uint8_t app_type() const { return this->pbuf_[OFFSET_APPTYPE]; } - - template typename std::enable_if::value, T>::type as() const { - return T(*this); - } - String to_string() const; - - protected: - uint8_t *pbuf_; - uint8_t length_() const { return this->pbuf_[OFFSET_LENGTH]; } -}; - -class BaseFrame : public Frame { - public: - BaseFrame() = delete; - BaseFrame(uint8_t *data) : Frame(data) {} - BaseFrame(const Frame &frame) : Frame(frame) {} - - // Check for valid - bool is_valid() const; - // Prepare for sending to device - void finalize(); - uint8_t get_type() const { return this->pbuf_[9]; } - void set_type(uint8_t value) { this->pbuf_[9] = value; } - bool has_response_type(uint8_t type) const { return this->resp_type_() == type; } - bool has_type(uint8_t type) const { return this->get_type() == type; } - - protected: - static const uint8_t PROGMEM CRC_TABLE[256]; - void set_bytemask_(uint8_t idx, uint8_t mask, bool state); - uint8_t resp_type_() const { return this->pbuf_[OFFSET_BODY]; } - bool has_valid_crc_() const; - bool has_valid_cs_() const; - void update_crc_(); - void update_cs_(); -}; - -template class StaticFrame : public T { - public: - // Default constructor - StaticFrame() : T(this->buf_) {} - // Copy constructor - StaticFrame(const Frame &src) : T(this->buf_) { - if (src.length_() < sizeof(this->buf_)) { - memcpy(this->buf_, src.data(), src.length_() + OFFSET_LENGTH); - } - } - // Constructor for RAM data - StaticFrame(const uint8_t *src) : T(this->buf_) { - const uint8_t len = src[OFFSET_LENGTH]; - if (len < sizeof(this->buf_)) { - memcpy(this->buf_, src, len + OFFSET_LENGTH); - } - } - // Constructor for PROGMEM data - StaticFrame(const __FlashStringHelper *pgm) : T(this->buf_) { - const uint8_t *src = reinterpret_cast(pgm); - const uint8_t len = pgm_read_byte(src + OFFSET_LENGTH); - if (len < sizeof(this->buf_)) { - memcpy_P(this->buf_, src, len + OFFSET_LENGTH); - } - } - - protected: - uint8_t buf_[buf_size]; -}; - -// Device network notification frame -class NotifyFrame : public midea_dongle::StaticFrame { - public: - NotifyFrame() : StaticFrame(FPSTR(NotifyFrame::INIT)) {} - void set_signal_strength(uint8_t value) { this->pbuf_[12] = value; } - uint8_t get_signal_strength() const { return this->pbuf_[12]; } - void set_connected(bool state) { this->pbuf_[18] = state ? 0 : 1; } - bool is_connected() const { return !this->pbuf_[18]; } - - private: - static const uint8_t PROGMEM INIT[]; -}; - -} // namespace midea_dongle -} // namespace esphome diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index c9f1c611a8..d76dc6bc34 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1085,3 +1085,45 @@ async def panasonic_action(var, config, args): cg.add(var.set_address(template_)) template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint32) cg.add(var.set_command(template_)) + + +# Midea +MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol( + "Midea" +) +MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase) +MIDEA_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t, cv.uint8_t)], + cv.Length(min=5, max=5), + ), + cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.uint8), + } +) + + +@register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA) +def midea_binary_sensor(var, config): + arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) + cg.add(var.set_code(arr_)) + + +@register_trigger("midea", MideaTrigger, MideaData) +def midea_trigger(var, config): + pass + + +@register_dumper("midea", MideaDumper) +def midea_dumper(var, config): + pass + + +@register_action( + "midea", + MideaAction, + MIDEA_SCHEMA, +) +async def midea_action(var, config, args): + arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) + cg.add(var.set_code(arr_)) diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp new file mode 100644 index 0000000000..baf64f246f --- /dev/null +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -0,0 +1,99 @@ +#include "midea_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.midea"; + +uint8_t MideaData::calc_cs_() const { + uint8_t cs = 0; + for (const uint8_t *it = this->data(); it != this->data() + OFFSET_CS; ++it) + cs -= reverse_bits_8(*it); + return reverse_bits_8(cs); +} + +bool MideaData::check_compliment(const MideaData &rhs) const { + const uint8_t *it0 = rhs.data(); + for (const uint8_t *it1 = this->data(); it1 != this->data() + this->size(); ++it0, ++it1) { + if (*it0 != ~(*it1)) + return false; + } + return true; +} + +void MideaProtocol::data(RemoteTransmitData *dst, const MideaData &src, bool compliment) { + for (const uint8_t *it = src.data(); it != src.data() + src.size(); ++it) { + const uint8_t data = compliment ? ~(*it) : *it; + for (uint8_t mask = 128; mask; mask >>= 1) { + if (data & mask) + one(dst); + else + zero(dst); + } + } +} + +void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &data) { + dst->set_carrier_frequency(38000); + dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 2); + MideaProtocol::header(dst); + MideaProtocol::data(dst, data); + MideaProtocol::footer(dst); + MideaProtocol::header(dst); + MideaProtocol::data(dst, data, true); + MideaProtocol::footer(dst); +} + +bool MideaProtocol::expect_one(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_zero(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_header(RemoteReceiveData &src) { + if (!src.peek_item(HEADER_HIGH_US, HEADER_LOW_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_footer(RemoteReceiveData &src) { + if (!src.peek_item(BIT_HIGH_US, MIN_GAP_US)) + return false; + src.advance(2); + return true; +} + +bool MideaProtocol::expect_data(RemoteReceiveData &src, MideaData &out) { + for (uint8_t *dst = out.data(); dst != out.data() + out.size(); ++dst) { + for (uint8_t mask = 128; mask; mask >>= 1) { + if (MideaProtocol::expect_one(src)) + *dst |= mask; + else if (!MideaProtocol::expect_zero(src)) + return false; + } + } + return true; +} + +optional MideaProtocol::decode(RemoteReceiveData src) { + MideaData out, inv; + if (MideaProtocol::expect_header(src) && MideaProtocol::expect_data(src, out) && MideaProtocol::expect_footer(src) && + out.is_valid() && MideaProtocol::expect_data(src, inv) && out.check_compliment(inv)) + return out; + return {}; +} + +void MideaProtocol::dump(const MideaData &data) { ESP_LOGD(TAG, "Received Midea: %s", data.to_string().c_str()); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h new file mode 100644 index 0000000000..9b0d156617 --- /dev/null +++ b/esphome/components/remote_base/midea_protocol.h @@ -0,0 +1,105 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +class MideaData { + public: + // Make zero-filled + MideaData() { memset(this->data_, 0, sizeof(this->data_)); } + // Make from initializer_list + MideaData(std::initializer_list data) { std::copy(data.begin(), data.end(), this->data()); } + // Make from vector + MideaData(const std::vector &data) { + memcpy(this->data_, data.data(), std::min(data.size(), sizeof(this->data_))); + } + // Make 40-bit copy from PROGMEM array + MideaData(const uint8_t *data) { memcpy_P(this->data_, data, OFFSET_CS); } + // Default copy constructor + MideaData(const MideaData &) = default; + + uint8_t *data() { return this->data_; } + const uint8_t *data() const { return this->data_; } + uint8_t size() const { return sizeof(this->data_); } + bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); } + void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } + bool check_compliment(const MideaData &rhs) const; + std::string to_string() const { return hexencode(*this); } + // compare only 40-bits + bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } + enum MideaDataType : uint8_t { + MIDEA_TYPE_COMMAND = 0xA1, + MIDEA_TYPE_SPECIAL = 0xA2, + MIDEA_TYPE_FOLLOW_ME = 0xA4, + }; + MideaDataType type() const { return static_cast(this->data_[0]); } + template T to() const { return T(*this); } + + protected: + void set_value_(uint8_t offset, uint8_t val_mask, uint8_t shift, uint8_t val) { + data_[offset] &= ~(val_mask << shift); + data_[offset] |= (val << shift); + } + static const uint8_t OFFSET_CS = 5; + // 48-bits data + uint8_t data_[6]; + // Calculate checksum + uint8_t calc_cs_() const; +}; + +class MideaProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const MideaData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const MideaData &data) override; + + protected: + static const int32_t TICK_US = 560; + static const int32_t HEADER_HIGH_US = 8 * TICK_US; + static const int32_t HEADER_LOW_US = 8 * TICK_US; + static const int32_t BIT_HIGH_US = 1 * TICK_US; + static const int32_t BIT_ONE_LOW_US = 3 * TICK_US; + static const int32_t BIT_ZERO_LOW_US = 1 * TICK_US; + static const int32_t MIN_GAP_US = 10 * TICK_US; + static void one(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); } + static void zero(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); } + static void header(RemoteTransmitData *dst) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); } + static void footer(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, MIN_GAP_US); } + static void data(RemoteTransmitData *dst, const MideaData &src, bool compliment = false); + static bool expect_one(RemoteReceiveData &src); + static bool expect_zero(RemoteReceiveData &src); + static bool expect_header(RemoteReceiveData &src); + static bool expect_footer(RemoteReceiveData &src); + static bool expect_data(RemoteReceiveData &src, MideaData &out); +}; + +class MideaBinarySensor : public RemoteReceiverBinarySensorBase { + public: + bool matches(RemoteReceiveData src) override { + auto data = MideaProtocol().decode(src); + return data.has_value() && data.value() == this->data_; + } + void set_code(const uint8_t *code) { this->data_ = code; } + + protected: + MideaData data_; +}; + +using MideaTrigger = RemoteReceiverTrigger; +using MideaDumper = RemoteReceiverDumper; + +template class MideaAction : public RemoteTransmitterActionBase { + TEMPLATABLE_VALUE(const uint8_t *, code) + void encode(RemoteTransmitData *dst, Ts... x) override { + MideaData data = this->code_.value(x...); + data.finalize(); + MideaProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index aff03b244b..07d0079172 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -69,6 +69,7 @@ CONF_ATTENUATION = "attenuation" CONF_ATTRIBUTE = "attribute" CONF_AUTH = "auth" CONF_AUTO_MODE = "auto_mode" +CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" CONF_AVAILABILITY = "availability" CONF_AWAY = "away" @@ -78,6 +79,7 @@ CONF_BASELINE = "baseline" CONF_BATTERY_LEVEL = "battery_level" CONF_BATTERY_VOLTAGE = "battery_voltage" CONF_BAUD_RATE = "baud_rate" +CONF_BEEPER = "beeper" CONF_BELOW = "below" CONF_BINARY = "binary" CONF_BINARY_SENSOR = "binary_sensor" @@ -615,6 +617,10 @@ CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action" CONF_SUPPLEMENTAL_COOLING_DELTA = "supplemental_cooling_delta" CONF_SUPPLEMENTAL_HEATING_ACTION = "supplemental_heating_action" CONF_SUPPLEMENTAL_HEATING_DELTA = "supplemental_heating_delta" +CONF_SUPPORTED_FAN_MODES = "supported_fan_modes" +CONF_SUPPORTED_MODES = "supported_modes" +CONF_SUPPORTED_PRESETS = "supported_presets" +CONF_SUPPORTED_SWING_MODES = "supported_swing_modes" CONF_SUPPORTS_COOL = "supports_cool" CONF_SUPPORTS_HEAT = "supports_heat" CONF_SWING_BOTH_ACTION = "swing_both_action" diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index e4535e77d9..cd3081998b 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -20,6 +20,7 @@ const float PROCESSOR = 400.0; const float BLUETOOTH = 350.0f; const float AFTER_BLUETOOTH = 300.0f; const float WIFI = 250.0f; +const float BEFORE_CONNECTION = 220.0f; const float AFTER_WIFI = 200.0f; const float AFTER_CONNECTION = 100.0f; const float LATE = -100.0f; diff --git a/esphome/core/component.h b/esphome/core/component.h index a84f612dd9..ea87ebcdfe 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -29,6 +29,8 @@ extern const float PROCESSOR; extern const float BLUETOOTH; extern const float AFTER_BLUETOOTH; extern const float WIFI; +/// For components that should be initialized after WiFi and before API is connected. +extern const float BEFORE_CONNECTION; /// For components that should be initialized after WiFi is connected. extern const float AFTER_WIFI; /// For components that should be initialized after a data connection (API/MQTT) is connected. diff --git a/platformio.ini b/platformio.ini index c280c54a21..88b1000d1d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,6 +36,7 @@ lib_deps = 6306@1.0.3 ; HM3301 glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr + dudanov/MideaUART@1.1.0 ; used by midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE diff --git a/tests/test1.yaml b/tests/test1.yaml index da3289843d..cd4179f394 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1608,29 +1608,84 @@ climate: name: Toshiba Climate - platform: hitachi_ac344 name: Hitachi Climate - - platform: midea_ac + - platform: midea + id: midea_unit + uart_id: uart0 + name: Midea Climate + transmitter_id: + period: 1s + num_attempts: 5 + timeout: 2s + beeper: false + autoconf: true visual: - min_temperature: 18 °C - max_temperature: 25 °C - temperature_step: 0.1 °C - name: 'Electrolux EACS' - beeper: true + min_temperature: 17 °C + max_temperature: 30 °C + temperature_step: 0.5 °C + supported_modes: + - FAN_ONLY + - HEAT_COOL + - COOL + - HEAT + - DRY + custom_fan_modes: + - SILENT + - TURBO + supported_presets: + - ECO + - BOOST + - SLEEP + custom_presets: + - FREEZE_PROTECTION + supported_swing_modes: + - VERTICAL + - HORIZONTAL + - BOTH outdoor_temperature: - name: 'Temp' + name: "Temp" power_usage: - name: 'Power' + name: "Power" humidity_setpoint: - name: 'Hum' + name: "Humidity" - platform: anova name: Anova cooker ble_client_id: ble_blah unit_of_measurement: c -midea_dongle: - uart_id: uart0 - strength_icon: true +script: + - id: climate_custom + then: + - climate.control: + id: midea_unit + custom_preset: FREEZE_PROTECTION + custom_fan_mode: SILENT + - id: climate_preset + then: + - climate.control: + id: midea_unit + preset: SLEEP switch: + - platform: template + name: MIDEA_AC_TOGGLE_LIGHT + turn_on_action: + midea_ac.display_toggle: + - platform: template + name: MIDEA_AC_SWING_STEP + turn_on_action: + midea_ac.swing_step: + - platform: template + name: MIDEA_AC_BEEPER_CONTROL + optimistic: true + turn_on_action: + midea_ac.beeper_on: + turn_off_action: + midea_ac.beeper_off: + - platform: template + name: MIDEA_RAW + turn_on_action: + remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] - platform: gpio name: 'MCP23S08 Pin #0' pin: diff --git a/tests/test3.yaml b/tests/test3.yaml index e35c1e611c..c012871125 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -748,17 +748,6 @@ script: - id: my_script then: - lambda: 'ESP_LOGD("main", "Hello World!");' - - id: climate_custom - then: - - climate.control: - id: midea_ac_unit - custom_preset: FREEZE_PROTECTION - custom_fan_mode: SILENT - - id: climate_preset - then: - - climate.control: - id: midea_ac_unit - preset: SLEEP sm2135: data_pin: GPIO12 @@ -949,32 +938,6 @@ climate: kp: 0.0 ki: 0.0 kd: 0.0 - - platform: midea_ac - id: midea_ac_unit - visual: - min_temperature: 18 °C - max_temperature: 25 °C - temperature_step: 0.1 °C - name: "Electrolux EACS" - beeper: true - custom_fan_modes: - - SILENT - - TURBO - preset_eco: true - preset_sleep: true - preset_boost: true - custom_presets: - - FREEZE_PROTECTION - outdoor_temperature: - name: "Temp" - power_usage: - name: "Power" - humidity_setpoint: - name: "Hum" - -midea_dongle: - uart_id: uart1 - strength_icon: true cover: - platform: endstop From 9e5cd0da513ab0dd650afe13bbfdff3880c73d24 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 8 Sep 2021 23:19:43 +0200 Subject: [PATCH 193/285] ccs811: publish firmware version; log bootloader and HW version; fix a bug (#2006) --- CODEOWNERS | 1 + esphome/components/ccs811/ccs811.cpp | 47 +++++++++++++++++++++++----- esphome/components/ccs811/ccs811.h | 5 ++- esphome/components/ccs811/sensor.py | 18 ++++++++++- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index dedd8acc1e..3ae9f71ead 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/canbus/* @danielschramm @mvturnho esphome/components/captive_portal/* @OttoWinter +esphome/components/ccs811/* @habbie esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index dec070a9b2..08df6f7774 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -16,7 +16,7 @@ static const char *const TAG = "ccs811"; return; \ } -#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED) +#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICATION_FAILED) void CCS811Component::setup() { // page 9 programming guide - hwid is always 0x81 @@ -38,12 +38,14 @@ void CCS811Component::setup() { // set MEAS_MODE (page 5) uint8_t meas_mode = 0; uint32_t interval = this->get_update_interval(); - if (interval <= 1000) - meas_mode = 1 << 4; - else if (interval <= 10000) - meas_mode = 2 << 4; + if (interval >= 60 * 1000) + meas_mode = 3 << 4; // sensor takes a reading every 60 seconds + else if (interval >= 10 * 1000) + meas_mode = 2 << 4; // sensor takes a reading every 10 seconds + else if (interval >= 1 * 1000) + meas_mode = 1 << 4; // sensor takes a reading every second else - meas_mode = 3 << 4; + meas_mode = 4 << 4; // sensor takes a reading every 250ms CHECKED_IO(this->write_byte(0x01, meas_mode)) @@ -51,6 +53,36 @@ void CCS811Component::setup() { // baseline available, write to sensor this->write_bytes(0x11, decode_uint16(*this->baseline_)); } + + auto hardware_version_data = this->read_bytes<1>(0x21); + auto bootloader_version_data = this->read_bytes<2>(0x23); + auto application_version_data = this->read_bytes<2>(0x24); + + uint8_t hardware_version = 0; + uint16_t bootloader_version = 0; + uint16_t application_version = 0; + + if (hardware_version_data.has_value()) { + hardware_version = (*hardware_version_data)[0]; + } + + if (bootloader_version_data.has_value()) { + bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]); + } + + if (application_version_data.has_value()) { + application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]); + } + + ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version, + bootloader_version, application_version); + if (this->version_ != nullptr) { + char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room + sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15), + (application_version >> 4 & 15), application_version); + ESP_LOGD(TAG, "publishing version state: %s", version); + this->version_->publish_state(version); + } } void CCS811Component::update() { if (!this->status_has_data_()) @@ -117,6 +149,7 @@ void CCS811Component::dump_config() { LOG_UPDATE_INTERVAL(this) LOG_SENSOR(" ", "CO2 Sensor", this->co2_) LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_) + LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_) if (this->baseline_) { ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_); } else { @@ -124,7 +157,7 @@ void CCS811Component::dump_config() { } if (this->is_failed()) { switch (this->error_code_) { - case COMMUNICAITON_FAILED: + case COMMUNICATION_FAILED: ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); break; case INVALID_ID: diff --git a/esphome/components/ccs811/ccs811.h b/esphome/components/ccs811/ccs811.h index cea919c9a5..8a0d60d002 100644 --- a/esphome/components/ccs811/ccs811.h +++ b/esphome/components/ccs811/ccs811.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/i2c/i2c.h" namespace esphome { @@ -12,6 +13,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { public: void set_co2(sensor::Sensor *co2) { co2_ = co2; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; } + void set_version(text_sensor::TextSensor *version) { version_ = version; } void set_baseline(uint16_t baseline) { baseline_ = baseline; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } @@ -34,7 +36,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { enum ErrorCode { UNKNOWN, - COMMUNICAITON_FAILED, + COMMUNICATION_FAILED, INVALID_ID, SENSOR_REPORTED_ERROR, APP_INVALID, @@ -43,6 +45,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *co2_{nullptr}; sensor::Sensor *tvoc_{nullptr}; + text_sensor::TextSensor *version_{nullptr}; optional baseline_{}; /// Input sensor for humidity reading. sensor::Sensor *humidity_{nullptr}; diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index c177ed6b5c..bb8200273d 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -1,9 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor +from esphome.components import i2c, sensor, text_sensor from esphome.const import ( + CONF_ICON, CONF_ID, ICON_RADIATOR, + ICON_RESTART, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, @@ -14,9 +16,12 @@ from esphome.const import ( CONF_TEMPERATURE, CONF_TVOC, CONF_HUMIDITY, + CONF_VERSION, ICON_MOLECULE_CO2, ) +AUTO_LOAD = ["text_sensor"] +CODEOWNERS = ["@habbie"] DEPENDENCIES = ["i2c"] ccs811_ns = cg.esphome_ns.namespace("ccs811") @@ -42,6 +47,12 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon, + } + ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor), @@ -62,6 +73,11 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_TVOC]) cg.add(var.set_tvoc(sens)) + if CONF_VERSION in config: + sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID]) + await text_sensor.register_text_sensor(sens, config[CONF_VERSION]) + cg.add(var.set_version(sens)) + if CONF_BASELINE in config: cg.add(var.set_baseline(config[CONF_BASELINE])) From e5051eefbcf1fecf2f02904c40b862bf899ec8f4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 8 Sep 2021 23:22:47 +0200 Subject: [PATCH 194/285] API encryption (#2254) --- esphome/components/api/__init__.py | 33 ++ esphome/components/api/api_connection.cpp | 6 + esphome/components/api/api_frame_helper.cpp | 612 ++++++++++++++++++++ esphome/components/api/api_frame_helper.h | 76 +++ esphome/components/api/api_noise_context.h | 23 + esphome/components/api/api_server.h | 10 + esphome/core/defines.h | 3 + esphome/core/helpers.cpp | 9 + esphome/core/helpers.h | 2 + platformio.ini | 1 + tests/test3.yaml | 2 + 11 files changed, 777 insertions(+) create mode 100644 esphome/components/api/api_noise_context.h diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index fc140dc7d2..3705f0d7ca 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -1,3 +1,5 @@ +import base64 + import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -6,6 +8,7 @@ from esphome.const import ( CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, + CONF_KEY, CONF_PASSWORD, CONF_PORT, CONF_REBOOT_TIMEOUT, @@ -41,6 +44,22 @@ SERVICE_ARG_NATIVE_TYPES = { "float[]": cg.std_vector.template(float), "string[]": cg.std_vector.template(cg.std_string), } +CONF_ENCRYPTION = "encryption" + + +def validate_encryption_key(value): + value = cv.string_strict(value) + try: + decoded = base64.b64decode(value, validate=True) + except ValueError as err: + raise cv.Invalid("Invalid key format, please check it's using base64") from err + + if len(decoded) != 32: + raise cv.Invalid("Encryption key must be base64 and 32 bytes long") + + # Return original data for roundtrip conversion + return value + CONFIG_SCHEMA = cv.Schema( { @@ -63,6 +82,11 @@ CONFIG_SCHEMA = cv.Schema( ), } ), + cv.Optional(CONF_ENCRYPTION): cv.Schema( + { + cv.Required(CONF_KEY): validate_encryption_key, + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -92,6 +116,15 @@ async def to_code(config): cg.add(var.register_user_service(trigger)) await automation.build_automation(trigger, func_args, conf) + if CONF_ENCRYPTION in config: + conf = config[CONF_ENCRYPTION] + decoded = base64.b64decode(conf[CONF_KEY]) + cg.add(var.set_noise_psk(list(decoded))) + cg.add_define("USE_API_NOISE") + cg.add_library("esphome/noise-c", "0.1.1") + else: + cg.add_define("USE_API_PLAINTEXT") + cg.add_define("USE_API") cg.add_global(api_ns.using) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bce0b0bab8..650f4f6f6e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -23,7 +23,13 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { this->proto_write_buffer_.reserve(64); +#if defined(USE_API_PLAINTEXT) helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; +#elif defined(USE_API_NOISE) + helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; +#else +#error "No frame helper defined" +#endif } void APIConnection::start() { this->last_traffic_ = millis(); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index f903ab8656..26fbf1269f 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -19,6 +19,617 @@ bool is_would_block(ssize_t ret) { #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) +#ifdef USE_API_NOISE +static const char *const PROLOGUE_INIT = "NoiseAPIInit"; + +/// Convert a noise error code to a readable error +std::string noise_err_to_str(int err) { + if (err == NOISE_ERROR_NO_MEMORY) + return "NO_MEMORY"; + if (err == NOISE_ERROR_UNKNOWN_ID) + return "UNKNOWN_ID"; + if (err == NOISE_ERROR_UNKNOWN_NAME) + return "UNKNOWN_NAME"; + if (err == NOISE_ERROR_MAC_FAILURE) + return "MAC_FAILURE"; + if (err == NOISE_ERROR_NOT_APPLICABLE) + return "NOT_APPLICABLE"; + if (err == NOISE_ERROR_SYSTEM) + return "SYSTEM"; + if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED) + return "REMOTE_KEY_REQUIRED"; + if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED) + return "LOCAL_KEY_REQUIRED"; + if (err == NOISE_ERROR_PSK_REQUIRED) + return "PSK_REQUIRED"; + if (err == NOISE_ERROR_INVALID_LENGTH) + return "INVALID_LENGTH"; + if (err == NOISE_ERROR_INVALID_PARAM) + return "INVALID_PARAM"; + if (err == NOISE_ERROR_INVALID_STATE) + return "INVALID_STATE"; + if (err == NOISE_ERROR_INVALID_NONCE) + return "INVALID_NONCE"; + if (err == NOISE_ERROR_INVALID_PRIVATE_KEY) + return "INVALID_PRIVATE_KEY"; + if (err == NOISE_ERROR_INVALID_PUBLIC_KEY) + return "INVALID_PUBLIC_KEY"; + if (err == NOISE_ERROR_INVALID_FORMAT) + return "INVALID_FORMAT"; + if (err == NOISE_ERROR_INVALID_SIGNATURE) + return "INVALID_SIGNATURE"; + return to_string(err); +} + +/// Initialize the frame helper, returns OK if successful. +APIError APINoiseFrameHelper::init() { + if (state_ != State::INITIALIZE || socket_ == nullptr) { + HELPER_LOG("Bad state for init %d", (int) state_); + return APIError::BAD_STATE; + } + int err = socket_->setblocking(false); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nonblocking failed with errno %d", errno); + return APIError::TCP_NONBLOCKING_FAILED; + } + int enable = 1; + err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("Setting nodelay failed with errno %d", errno); + return APIError::TCP_NODELAY_FAILED; + } + + // init prologue + prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); + + state_ = State::CLIENT_HELLO; + return APIError::OK; +} +/// Run through handshake messages (if in that phase) +APIError APINoiseFrameHelper::loop() { + APIError err = state_action_(); + if (err == APIError::WOULD_BLOCK) + return APIError::OK; + if (err != APIError::OK) + return err; + if (!tx_buf_.empty()) { + err = try_send_tx_buf_(); + if (err != APIError::OK) { + return err; + } + } + return APIError::OK; +} + +/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter + * + * @param frame: The struct to hold the frame information in. + * msg_start: points to the start of the payload - this pointer is only valid until the next + * try_receive_raw_ call + * + * @return 0 if a full packet is in rx_buf_ + * @return -1 if error, check errno. + * + * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later. + * errno ENOMEM: Not enough memory for reading packet. + * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. + * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. + */ +APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { + int err; + APIError aerr; + + if (frame == nullptr) { + HELPER_LOG("Bad argument for try_read_frame_"); + return APIError::BAD_ARG; + } + + // read header + if (rx_header_buf_len_ < 3) { + // no header information yet + size_t to_read = 3 - rx_header_buf_len_; + ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_header_buf_len_ += received; + if (received != to_read) { + // not a full read + return APIError::WOULD_BLOCK; + } + + // header reading done + } + + // read body + uint8_t indicator = rx_header_buf_[0]; + if (indicator != 0x01) { + state_ = State::FAILED; + HELPER_LOG("Bad indicator byte %u", indicator); + return APIError::BAD_INDICATOR; + } + + uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; + + if (state_ != State::DATA && msg_size > 128) { + // for handshake message only permit up to 128 bytes + state_ = State::FAILED; + HELPER_LOG("Bad packet len for handshake: %d", msg_size); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } + + // reserve space for body + if (rx_buf_.size() != msg_size) { + rx_buf_.resize(msg_size); + } + + if (rx_buf_len_ < msg_size) { + // more data to read + size_t to_read = msg_size - rx_buf_len_; + ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); + if (is_would_block(received)) { + return APIError::WOULD_BLOCK; + } else if (received == -1) { + state_ = State::FAILED; + HELPER_LOG("Socket read failed with errno %d", errno); + return APIError::SOCKET_READ_FAILED; + } + rx_buf_len_ += received; + if (received != to_read) { + // not all read + return APIError::WOULD_BLOCK; + } + } + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + frame->msg = std::move(rx_buf_); + // consume msg + rx_buf_ = {}; + rx_buf_len_ = 0; + rx_header_buf_len_ = 0; + return APIError::OK; +} + +/** To be called from read/write methods. + * + * This method runs through the internal handshake methods, if in that state. + * + * If the handshake is still active when this method returns and a read/write can't take place at + * the moment, returns WOULD_BLOCK. + * If an error occured, returns that error. Only returns OK if the transport is ready for data + * traffic. + */ +APIError APINoiseFrameHelper::state_action_() { + int err; + APIError aerr; + if (state_ == State::INITIALIZE) { + HELPER_LOG("Bad state for method: %d", (int) state_); + return APIError::BAD_STATE; + } + if (state_ == State::CLIENT_HELLO) { + // waiting for client hello + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + // ignore contents, may be used in future for flags + prologue_.push_back((uint8_t)(frame.msg.size() >> 8)); + prologue_.push_back((uint8_t) frame.msg.size()); + prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); + + state_ = State::SERVER_HELLO; + } + if (state_ == State::SERVER_HELLO) { + // send server hello + uint8_t msg[1]; + msg[0] = 0x01; // chosen proto + aerr = write_frame_(msg, 1); + if (aerr != APIError::OK) + return aerr; + + // start handshake + aerr = init_handshake_(); + if (aerr != APIError::OK) + return aerr; + + state_ = State::HANDSHAKE; + } + if (state_ == State::HANDSHAKE) { + int action = noise_handshakestate_get_action(handshake_); + if (action == NOISE_ACTION_READ_MESSAGE) { + // waiting for handshake msg + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr == APIError::BAD_INDICATOR) { + send_explicit_handshake_reject_("Bad indicator byte"); + return aerr; + } + if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { + send_explicit_handshake_reject_("Bad handshake packet len"); + return aerr; + } + if (aerr != APIError::OK) + return aerr; + + if (frame.msg.empty()) { + send_explicit_handshake_reject_("Empty handshake message"); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } else if (frame.msg[0] != 0x00) { + HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); + send_explicit_handshake_reject_("Bad handshake error byte"); + return APIError::BAD_HANDSHAKE_PACKET_LEN; + } + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); + err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); + if (err != 0) { + // TODO: explicit rejection + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str()); + if (err == NOISE_ERROR_MAC_FAILURE) { + send_explicit_handshake_reject_("Handshake MAC failure"); + } else { + send_explicit_handshake_reject_("Handshake error"); + } + return APIError::HANDSHAKESTATE_READ_FAILED; + } + + aerr = check_handshake_finished_(); + if (aerr != APIError::OK) + return aerr; + } else if (action == NOISE_ACTION_WRITE_MESSAGE) { + uint8_t buffer[65]; + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); + + err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_WRITE_FAILED; + } + buffer[0] = 0x00; // success + + aerr = write_frame_(buffer, mbuf.size + 1); + if (aerr != APIError::OK) + return aerr; + aerr = check_handshake_finished_(); + if (aerr != APIError::OK) + return aerr; + } else { + // bad state for action + state_ = State::FAILED; + HELPER_LOG("Bad action for handshake: %d", action); + return APIError::HANDSHAKESTATE_BAD_STATE; + } + } + if (state_ == State::CLOSED || state_ == State::FAILED) { + return APIError::BAD_STATE; + } + return APIError::OK; +} +void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { + std::vector data; + data.reserve(reason.size() + 1); + data[0] = 0x01; // failure + for (size_t i = 0; i < reason.size(); i++) { + data[i + 1] = (uint8_t) reason[i]; + } + write_frame_(data.data(), data.size()); +} + +APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { + int err; + APIError aerr; + aerr = state_action_(); + if (aerr != APIError::OK) { + return aerr; + } + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + ParsedFrame frame; + aerr = try_read_frame_(&frame); + if (aerr != APIError::OK) + return aerr; + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size()); + err = noise_cipherstate_decrypt(recv_cipher_, &mbuf); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str()); + return APIError::CIPHERSTATE_DECRYPT_FAILED; + } + + size_t msg_size = mbuf.size; + uint8_t *msg_data = frame.msg.data(); + if (msg_size < 4) { + state_ = State::FAILED; + HELPER_LOG("Bad data packet: size %d too short", msg_size); + return APIError::BAD_DATA_PACKET; + } + + // uint16_t type; + // uint16_t data_len; + // uint8_t *data; + // uint8_t *padding; zero or more bytes to fill up the rest of the packet + uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; + uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; + if (data_len > msg_size - 4) { + state_ = State::FAILED; + HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size); + return APIError::BAD_DATA_PACKET; + } + + buffer->container = std::move(frame.msg); + buffer->data_offset = 4; + buffer->data_len = data_len; + buffer->type = type; + return APIError::OK; +} +bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } +APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { + int err; + APIError aerr; + aerr = state_action_(); + if (aerr != APIError::OK) { + return aerr; + } + + if (state_ != State::DATA) { + return APIError::WOULD_BLOCK; + } + + size_t padding = 0; + size_t msg_len = 4 + payload_len + padding; + size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_); + auto tmpbuf = std::unique_ptr{new (std::nothrow) uint8_t[frame_len]}; + if (tmpbuf == nullptr) { + HELPER_LOG("Could not allocate for writing packet"); + return APIError::OUT_OF_MEMORY; + } + + tmpbuf[0] = 0x01; // indicator + // tmpbuf[1], tmpbuf[2] to be set later + const uint8_t msg_offset = 3; + const uint8_t payload_offset = msg_offset + 4; + tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type + tmpbuf[msg_offset + 1] = (uint8_t) type; + tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len + tmpbuf[msg_offset + 3] = (uint8_t) payload_len; + // copy data + std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]); + // fill padding with zeros + std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0); + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset); + err = noise_cipherstate_encrypt(send_cipher_, &mbuf); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str()); + return APIError::CIPHERSTATE_ENCRYPT_FAILED; + } + + size_t total_len = 3 + mbuf.size; + tmpbuf[1] = (uint8_t)(mbuf.size >> 8); + tmpbuf[2] = (uint8_t) mbuf.size; + // write raw to not have two packets sent if NAGLE disabled + aerr = write_raw_(&tmpbuf[0], total_len); + if (aerr != APIError::OK) { + return aerr; + } + return APIError::OK; +} +APIError APINoiseFrameHelper::try_send_tx_buf_() { + // try send from tx_buf + while (state_ != State::CLOSED && !tx_buf_.empty()) { + ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); + if (sent == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent == 0) { + break; + } + // TODO: inefficient if multiple packets in txbuf + // replace with deque of buffers + tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent); + } + + return APIError::OK; +} +/** Write the data to the socket, or buffer it a write would block + * + * @param data The data to write + * @param len The length of data + */ +APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { + if (len == 0) + return APIError::OK; + int err; + APIError aerr; + + // uncomment for even more debugging + // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + + if (!tx_buf_.empty()) { + // try to empty tx_buf_ first + aerr = try_send_tx_buf_(); + if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK) + return aerr; + } + + if (!tx_buf_.empty()) { + // tx buf not empty, can't write now because then stream would be inconsistent + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } + + ssize_t sent = socket_->write(data, len); + if (is_would_block(sent)) { + // operation would block, add buffer to tx_buf + tx_buf_.insert(tx_buf_.end(), data, data + len); + return APIError::OK; + } else if (sent == -1) { + // an error occured + state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", errno); + return APIError::SOCKET_WRITE_FAILED; + } else if (sent != len) { + // partially sent, add end to tx_buf + tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + return APIError::OK; + } + // fully sent + return APIError::OK; +} +APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { + APIError aerr; + + uint8_t header[3]; + header[0] = 0x01; // indicator + header[1] = (uint8_t)(len >> 8); + header[2] = (uint8_t) len; + + aerr = write_raw_(header, 3); + if (aerr != APIError::OK) + return aerr; + aerr = write_raw_(data, len); + return aerr; +} + +/** Initiate the data structures for the handshake. + * + * @return 0 on success, -1 on error (check errno) + */ +APIError APINoiseFrameHelper::init_handshake_() { + int err; + memset(&nid_, 0, sizeof(nid_)); + // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; + // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto)); + nid_.pattern_id = NOISE_PATTERN_NN; + nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY; + nid_.dh_id = NOISE_DH_CURVE25519; + nid_.prefix_id = NOISE_PREFIX_STANDARD; + nid_.hybrid_id = NOISE_DH_NONE; + nid_.hash_id = NOISE_HASH_SHA256; + nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0; + + err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + + const auto &psk = ctx_->get_psk(); + err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + + err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + // set_prologue copies it into handshakestate, so we can get rid of it now + prologue_ = {}; + + err = noise_handshakestate_start(handshake_); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SETUP_FAILED; + } + return APIError::OK; +} + +APIError APINoiseFrameHelper::check_handshake_finished_() { + assert(state_ == State::HANDSHAKE); + + int action = noise_handshakestate_get_action(handshake_); + if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE) + return APIError::OK; + if (action != NOISE_ACTION_SPLIT) { + state_ = State::FAILED; + HELPER_LOG("Bad action for handshake: %d", action); + return APIError::HANDSHAKESTATE_BAD_STATE; + } + int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); + if (err != 0) { + state_ = State::FAILED; + HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str()); + return APIError::HANDSHAKESTATE_SPLIT_FAILED; + } + + HELPER_LOG("Handshake complete!"); + noise_handshakestate_free(handshake_); + handshake_ = nullptr; + state_ = State::DATA; + return APIError::OK; +} + +APINoiseFrameHelper::~APINoiseFrameHelper() { + if (handshake_ != nullptr) { + noise_handshakestate_free(handshake_); + handshake_ = nullptr; + } + if (send_cipher_ != nullptr) { + noise_cipherstate_free(send_cipher_); + send_cipher_ = nullptr; + } + if (recv_cipher_ != nullptr) { + noise_cipherstate_free(recv_cipher_); + recv_cipher_ = nullptr; + } +} + +APIError APINoiseFrameHelper::close() { + state_ = State::CLOSED; + int err = socket_->close(); + if (err == -1) + return APIError::CLOSE_FAILED; + return APIError::OK; +} +APIError APINoiseFrameHelper::shutdown(int how) { + int err = socket_->shutdown(how); + if (err == -1) + return APIError::SHUTDOWN_FAILED; + if (how == SHUT_RDWR) { + state_ = State::CLOSED; + } + return APIError::OK; +} +extern "C" { +// declare how noise generates random bytes (here with a good HWRNG based on the RF system) +void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast(output), len); } +} +#endif // USE_API_NOISE + +#ifdef USE_API_PLAINTEXT + /// Initialize the frame helper, returns OK if successful. APIError APIPlaintextFrameHelper::init() { if (state_ != State::INITIALIZE || socket_ == nullptr) { @@ -289,6 +900,7 @@ APIError APIPlaintextFrameHelper::shutdown(int how) { } return APIError::OK; } +#endif // USE_API_PLAINTEXT } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 14a0760c25..7189bc4b4b 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -5,7 +5,12 @@ #include "esphome/core/defines.h" +#ifdef USE_API_NOISE +#include "noise/protocol.h" +#endif + #include "esphome/components/socket/socket.h" +#include "api_noise_context.h" namespace esphome { namespace api { @@ -27,6 +32,7 @@ struct PacketBuffer { enum class APIError : int { OK = 0, WOULD_BLOCK = 1001, + BAD_HANDSHAKE_PACKET_LEN = 1002, BAD_INDICATOR = 1003, BAD_DATA_PACKET = 1004, TCP_NODELAY_FAILED = 1005, @@ -37,7 +43,14 @@ enum class APIError : int { BAD_ARG = 1010, SOCKET_READ_FAILED = 1011, SOCKET_WRITE_FAILED = 1012, + HANDSHAKESTATE_READ_FAILED = 1013, + HANDSHAKESTATE_WRITE_FAILED = 1014, + HANDSHAKESTATE_BAD_STATE = 1015, + CIPHERSTATE_DECRYPT_FAILED = 1016, + CIPHERSTATE_ENCRYPT_FAILED = 1017, OUT_OF_MEMORY = 1018, + HANDSHAKESTATE_SETUP_FAILED = 1019, + HANDSHAKESTATE_SPLIT_FAILED = 1020, }; class APIFrameHelper { @@ -53,6 +66,68 @@ class APIFrameHelper { // Give this helper a name for logging virtual void set_log_info(std::string info) = 0; }; + +#ifdef USE_API_NOISE +class APINoiseFrameHelper : public APIFrameHelper { + public: + APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx) + : socket_(std::move(socket)), ctx_(ctx) {} + ~APINoiseFrameHelper(); + APIError init() override; + APIError loop() override; + APIError read_packet(ReadPacketBuffer *buffer) override; + bool can_write_without_blocking() override; + APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override; + std::string getpeername() override { return socket_->getpeername(); } + APIError close() override; + APIError shutdown(int how) override; + // Give this helper a name for logging + void set_log_info(std::string info) override { info_ = std::move(info); } + + protected: + struct ParsedFrame { + std::vector msg; + }; + + APIError state_action_(); + APIError try_read_frame_(ParsedFrame *frame); + APIError try_send_tx_buf_(); + APIError write_frame_(const uint8_t *data, size_t len); + APIError write_raw_(const uint8_t *data, size_t len); + APIError init_handshake_(); + APIError check_handshake_finished_(); + void send_explicit_handshake_reject_(const std::string &reason); + + std::unique_ptr socket_; + + std::string info_; + uint8_t rx_header_buf_[3]; + size_t rx_header_buf_len_ = 0; + std::vector rx_buf_; + size_t rx_buf_len_ = 0; + + std::vector tx_buf_; + std::vector prologue_; + + std::shared_ptr ctx_; + NoiseHandshakeState *handshake_ = nullptr; + NoiseCipherState *send_cipher_ = nullptr; + NoiseCipherState *recv_cipher_ = nullptr; + NoiseProtocolId nid_; + + enum class State { + INITIALIZE = 1, + CLIENT_HELLO = 2, + SERVER_HELLO = 3, + HANDSHAKE = 4, + DATA = 5, + CLOSED = 6, + FAILED = 7, + } state_ = State::INITIALIZE; +}; +#endif // USE_API_NOISE + +#ifdef USE_API_PLAINTEXT class APIPlaintextFrameHelper : public APIFrameHelper { public: APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} @@ -98,6 +173,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { FAILED = 4, } state_ = State::INITIALIZE; }; +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h new file mode 100644 index 0000000000..fba6b65a26 --- /dev/null +++ b/esphome/components/api/api_noise_context.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace api { + +#ifdef USE_API_NOISE +using psk_t = std::array; + +class APINoiseContext { + public: + void set_psk(psk_t psk) { psk_ = std::move(psk); } + const psk_t &get_psk() const { return psk_; } + + protected: + psk_t psk_; +}; +#endif // USE_API_NOISE + +} // namespace api +} // namespace esphome diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 7c42fe7dd5..e3fa6b18c9 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -11,6 +11,7 @@ #include "list_entities.h" #include "subscribe_state.h" #include "user_services.h" +#include "api_noise_context.h" namespace esphome { namespace api { @@ -30,6 +31,11 @@ class APIServer : public Component, public Controller { void set_password(const std::string &password); void set_reboot_timeout(uint32_t reboot_timeout); +#ifdef USE_API_NOISE + void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); } + std::shared_ptr get_noise_ctx() { return noise_ctx_; } +#endif // USE_API_NOISE + void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; @@ -89,6 +95,10 @@ class APIServer : public Component, public Controller { std::string password_; std::vector state_subs_; std::vector user_services_; + +#ifdef USE_API_NOISE + std::shared_ptr noise_ctx_ = std::make_shared(); +#endif // USE_API_NOISE }; extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index d73f7e9d00..3cca6445b5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -54,5 +54,8 @@ #define USE_SOCKET_IMPL_BSD_SOCKETS #endif +#define USE_API_PLAINTEXT +#define USE_API_NOISE + // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 9e9c775899..c5ff0102c3 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -55,6 +55,15 @@ double random_double() { return random_uint32() / double(UINT32_MAX); } float random_float() { return float(random_double()); } +void fill_random(uint8_t *data, size_t len) { +#ifdef ARDUINO_ARCH_ESP32 + esp_fill_random(data, len); +#else + int err = os_get_random(data, len); + assert(err == 0); +#endif +} + static uint32_t fast_random_seed = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void fast_random_set_seed(uint32_t seed) { fast_random_seed = seed; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 5868918cd6..60bc7a9ad3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -109,6 +109,8 @@ double random_double(); /// Returns a random float between 0 and 1. Essentially just casts random_double() to a float. float random_float(); +void fill_random(uint8_t *data, size_t len); + void fast_random_set_seed(uint32_t seed); uint32_t fast_random_32(); uint16_t fast_random_16(); diff --git a/platformio.ini b/platformio.ini index 88b1000d1d..f4dea3fcb9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,6 +36,7 @@ lib_deps = 6306@1.0.3 ; HM3301 glmnet/Dsmr@0.3 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr + esphome/noise-c@0.1.1 ; used by api dudanov/MideaUART@1.1.0 ; used by midea build_flags = diff --git a/tests/test3.yaml b/tests/test3.yaml index c012871125..5602481c36 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -22,6 +22,8 @@ api: port: 8000 password: 'pwd' reboot_timeout: 0min + encryption: + key: 'bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU=' services: - service: hello_world variables: From cc52f379335659eddd2a0a087d61bf9d7bb4ed2f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 9 Sep 2021 09:29:08 +1200 Subject: [PATCH 195/285] Revert "Dont dump legacy fields (#2241)" (#2259) This reverts commit 97eba1eecc7d1ee1aac8f3904315375dfd442b51. --- esphome/components/api/api_pb2.cpp | 41 +++++++++++++++++++++++++++++ script/api_protobuf/api_protobuf.py | 2 -- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index dab0b29127..d6b85d257c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -793,6 +793,10 @@ void CoverStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_state: "); + out.append(proto_enum_to_string(this->legacy_state)); + out.append("\n"); + out.append(" position: "); sprintf(buffer, "%g", this->position); out.append(buffer); @@ -876,6 +880,10 @@ void CoverCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_command)); out.append("\n"); + out.append(" legacy_command: "); + out.append(proto_enum_to_string(this->legacy_command)); + out.append("\n"); + out.append(" has_position: "); out.append(YESNO(this->has_position)); out.append("\n"); @@ -1322,6 +1330,22 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append("\n"); } + out.append(" legacy_supports_brightness: "); + out.append(YESNO(this->legacy_supports_brightness)); + out.append("\n"); + + out.append(" legacy_supports_rgb: "); + out.append(YESNO(this->legacy_supports_rgb)); + out.append("\n"); + + out.append(" legacy_supports_white_value: "); + out.append(YESNO(this->legacy_supports_white_value)); + out.append("\n"); + + out.append(" legacy_supports_color_temperature: "); + out.append(YESNO(this->legacy_supports_color_temperature)); + out.append("\n"); + out.append(" min_mireds: "); sprintf(buffer, "%g", this->min_mireds); out.append(buffer); @@ -2736,6 +2760,11 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { out.append(YESNO(this->bool_)); out.append("\n"); + out.append(" legacy_int: "); + sprintf(buffer, "%d", this->legacy_int); + out.append(buffer); + out.append("\n"); + out.append(" float_: "); sprintf(buffer, "%g", this->float_); out.append(buffer); @@ -3151,6 +3180,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); + out.append("\n"); + out.append(" supports_action: "); out.append(YESNO(this->supports_action)); out.append("\n"); @@ -3309,6 +3342,10 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); + out.append("\n"); + out.append(" action: "); out.append(proto_enum_to_string(this->action)); out.append("\n"); @@ -3508,6 +3545,10 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(YESNO(this->has_legacy_away)); out.append("\n"); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); + out.append("\n"); + out.append(" has_fan_mode: "); out.append(YESNO(this->has_fan_mode)); out.append("\n"); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7b9e7941ae..7ccdc5a24e 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -183,8 +183,6 @@ class TypeInfo: @property def dump_content(self): - if self.name.startswith("legacy_"): - return None o = f'out.append(" {self.name}: ");\n' o += self.dump(f"this->{self.field_name}") + "\n" o += f'out.append("\\n");\n' From f09aca48658dc6708099778df5c77a54f358c14f Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 8 Sep 2021 23:35:00 +0200 Subject: [PATCH 196/285] pm1006: add support for sending a measurement request (#2214) --- CODEOWNERS | 1 + esphome/components/pm1006/pm1006.cpp | 7 +++++++ esphome/components/pm1006/pm1006.h | 3 ++- esphome/components/pm1006/sensor.py | 23 ++++++++++++++++++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 3ae9f71ead..8aa96d14af 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -94,6 +94,7 @@ esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 +esphome/components/pm1006/* @habbie esphome/components/pmsa003i/* @sjtrny esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz diff --git a/esphome/components/pm1006/pm1006.cpp b/esphome/components/pm1006/pm1006.cpp index 9bedb3cfc0..1a89307eee 100644 --- a/esphome/components/pm1006/pm1006.cpp +++ b/esphome/components/pm1006/pm1006.cpp @@ -7,6 +7,7 @@ namespace pm1006 { static const char *const TAG = "pm1006"; static const uint8_t PM1006_RESPONSE_HEADER[] = {0x16, 0x11, 0x0B}; +static const uint8_t PM1006_REQUEST[] = {0x11, 0x02, 0x0B, 0x01, 0xE1}; void PM1006Component::setup() { // because this implementation is currently rx-only, there is nothing to setup @@ -15,9 +16,15 @@ void PM1006Component::setup() { void PM1006Component::dump_config() { ESP_LOGCONFIG(TAG, "PM1006:"); LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); + LOG_UPDATE_INTERVAL(this); this->check_uart_settings(9600); } +void PM1006Component::update() { + ESP_LOGV(TAG, "sending measurement request"); + this->write_array(PM1006_REQUEST, sizeof(PM1006_REQUEST)); +} + void PM1006Component::loop() { while (this->available() != 0) { this->read_byte(&this->data_[this->data_index_]); diff --git a/esphome/components/pm1006/pm1006.h b/esphome/components/pm1006/pm1006.h index 66f4cf0311..238ac67006 100644 --- a/esphome/components/pm1006/pm1006.h +++ b/esphome/components/pm1006/pm1006.h @@ -7,7 +7,7 @@ namespace esphome { namespace pm1006 { -class PM1006Component : public Component, public uart::UARTDevice { +class PM1006Component : public PollingComponent, public uart::UARTDevice { public: PM1006Component() = default; @@ -15,6 +15,7 @@ class PM1006Component : public Component, public uart::UARTDevice { void setup() override; void dump_config() override; void loop() override; + void update() override; float get_setup_priority() const override; diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 0423be61e2..1e648be199 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -4,12 +4,15 @@ from esphome.components import sensor, uart from esphome.const import ( CONF_ID, CONF_PM_2_5, + CONF_UPDATE_INTERVAL, DEVICE_CLASS_PM25, STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_BLUR, ) +from esphome.core import TimePeriodMilliseconds +CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] pm1006_ns = cg.esphome_ns.namespace("pm1006") @@ -30,10 +33,28 @@ CONFIG_SCHEMA = cv.All( } ) .extend(cv.COMPONENT_SCHEMA) - .extend(uart.UART_DEVICE_SCHEMA), + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.polling_component_schema("never")), ) +def validate_interval_uart(config): + require_tx = False + + interval = config.get(CONF_UPDATE_INTERVAL) + + if isinstance(interval, TimePeriodMilliseconds): + # 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects + require_tx = True + + uart.final_validate_device_schema( + "pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx + )(config) + + +FINAL_VALIDATE_SCHEMA = validate_interval_uart + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) From faf1c8bee8aff24ab331397529a75355f18011e8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 8 Sep 2021 16:42:35 -0500 Subject: [PATCH 197/285] SGP40 sensor start-up fix (#2178) --- esphome/components/sgp40/sgp40.cpp | 17 ++++++++++++----- esphome/components/sgp40/sgp40.h | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 8d93b3e1b1..a911c107b9 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -78,27 +78,28 @@ void SGP40Component::setup() { } void SGP40Component::self_test_() { - ESP_LOGD(TAG, "selfTest started"); + ESP_LOGD(TAG, "Self-test started"); if (!this->write_command_(SGP40_CMD_SELF_TEST)) { this->error_code_ = COMMUNICATION_FAILED; - ESP_LOGD(TAG, "selfTest communicatin failed"); + ESP_LOGD(TAG, "Self-test communication failed"); this->mark_failed(); } this->set_timeout(250, [this]() { uint16_t reply[1]; if (!this->read_data_(reply, 1)) { - ESP_LOGD(TAG, "selfTest read_data_ failed"); + ESP_LOGD(TAG, "Self-test read_data_ failed"); this->mark_failed(); return; } if (reply[0] == 0xD400) { - ESP_LOGD(TAG, "selfTest completed"); + this->self_test_complete_ = true; + ESP_LOGD(TAG, "Self-test completed"); return; } - ESP_LOGD(TAG, "selfTest failed"); + ESP_LOGD(TAG, "Self-test failed"); this->mark_failed(); }); } @@ -154,6 +155,12 @@ int32_t SGP40Component::measure_voc_index_() { */ uint16_t SGP40Component::measure_raw_() { float humidity = NAN; + + if (!this->self_test_complete_) { + ESP_LOGD(TAG, "Self-test not yet complete"); + return UINT16_MAX; + } + if (this->humidity_sensor_ != nullptr) { humidity = this->humidity_sensor_->state; } diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h index b9ea365169..62936102e7 100644 --- a/esphome/components/sgp40/sgp40.h +++ b/esphome/components/sgp40/sgp40.h @@ -68,6 +68,7 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2 int32_t seconds_since_last_store_; SGP40Baselines baselines_storage_; VocAlgorithmParams voc_algorithm_params_; + bool self_test_complete_; bool store_baseline_; int32_t state0_; int32_t state1_; From d2616cbdfc5e14df20c6ecbb1d324d1bcf6b0ec9 Mon Sep 17 00:00:00 2001 From: Stephen Tierney Date: Thu, 9 Sep 2021 08:14:08 +1000 Subject: [PATCH 198/285] PMSA003i Update state_class and async (#2216) * Update component (state_class and async) * No need to specify empty device class * Remove unused import Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/pmsa003i/sensor.py | 67 ++++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/esphome/components/pmsa003i/sensor.py b/esphome/components/pmsa003i/sensor.py index a9586d98cd..ceca791cd6 100644 --- a/esphome/components/pmsa003i/sensor.py +++ b/esphome/components/pmsa003i/sensor.py @@ -16,7 +16,7 @@ from esphome.const import ( DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, - DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, ) CODEOWNERS = ["@sjtrny"] @@ -39,40 +39,61 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PMSA003IComponent), cv.Optional(CONF_STANDARD_UNITS, default=True): cv.boolean, cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM1, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM25, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 2, - DEVICE_CLASS_PM10, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( - UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_COUNTS_PER_100ML, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) @@ -93,15 +114,15 @@ TYPES = { } -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield i2c.register_i2c_device(var, config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) cg.add(var.set_standard_units(config[CONF_STANDARD_UNITS])) for key, funcName in TYPES.items(): if key in config: - sens = yield sensor.new_sensor(config[key]) + sens = await sensor.new_sensor(config[key]) cg.add(getattr(var, funcName)(sens)) From bad161a5c1d87d335ab88b7ad3ddc0297f2520a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 9 Sep 2021 10:28:34 +1200 Subject: [PATCH 199/285] Bump version to 2021.10.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 07d0079172..f342609538 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.9.0-dev" +__version__ = "2021.10.0-dev" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From affaaf7d2c86c5b356886098876ad4b970ba848c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Sep 2021 12:10:28 +1200 Subject: [PATCH 200/285] Fix a few ESP32-C3 compiler issues (#2265) * Fix using Serial when using ESP32-C3 standard pins * Force type for std::min in pn532 * Fix variable size where size_t is different on exp32-c3 --- esphome/components/api/api_frame_helper.cpp | 2 +- esphome/components/pn532/pn532.cpp | 2 +- esphome/components/uart/uart_esp32.cpp | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 26fbf1269f..520a5c2caf 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -707,7 +707,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } size_t i = 1; - size_t consumed = 0; + uint32_t consumed = 0; auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed); if (!msg_size_varint.has_value()) { // not enough data there yet diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index fc84f30078..fcab9872c4 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -49,7 +49,7 @@ void PN532::setup() { } // Set up SAM (secure access module) - uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); + uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); if (!this->write_command_({ PN532_COMMAND_SAMCONFIGURATION, 0x01, // normal mode diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index 16d683e4a6..c672a34c36 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -73,7 +73,11 @@ void UARTComponent::setup() { // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. +#ifdef CONFIG_IDF_TARGET_ESP32C3 + if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) { +#else if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { +#endif this->hw_serial_ = &Serial; } else { this->hw_serial_ = new HardwareSerial(next_uart_num++); From 3d71e2e1890a94d0caee3dda2403184b98fdbfde Mon Sep 17 00:00:00 2001 From: poptix Date: Fri, 10 Sep 2021 04:05:25 -0500 Subject: [PATCH 201/285] sm300d2: Accept (undocumented) 0x80 checksum offset. (#2263) Co-authored-by: Matt Hallacy --- esphome/components/sm300d2/sm300d2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index 34d80349f9..b1787581ae 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -27,7 +27,8 @@ void SM300D2Sensor::update() { } uint16_t calculated_checksum = this->sm300d2_checksum_(response); - if (calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) { + if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) && + (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], calculated_checksum); this->status_set_warning(); From ede1de9021c2ffe9533ad7f05c929554834a6c13 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 12 Sep 2021 22:04:35 +0200 Subject: [PATCH 202/285] Drop obsolete comments from CONTRIBUTING.md (#2271) --- CONTRIBUTING.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43d5e79074..e96bb5745b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,6 @@ # Contributing to ESPHome -This python project is responsible for reading in YAML configuration files, -converting them to C++ code. This code is then converted to a platformio project and compiled -with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project. - -For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml +For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome Things to note when contributing: From 97a18717e6d3b39a281cd1403d03a304404321a0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 02:44:39 +0200 Subject: [PATCH 203/285] Disable automatic usage of SNTP servers from DHCP (#2273) --- esphome/components/wifi/wifi_component_esp32.cpp | 6 ++++++ esphome/components/wifi/wifi_component_esp8266.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index 57c4efcdd5..b56030db56 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -11,6 +11,7 @@ #endif #include "lwip/err.h" #include "lwip/dns.h" +#include "lwip/apps/sntp.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -92,6 +93,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { esp_err_t err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index ad1a64d1f4..de529ee3aa 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -16,6 +16,7 @@ extern "C" { #include "lwip/dns.h" #include "lwip/dhcp.h" #include "lwip/init.h" // LWIP_VERSION_ +#include "lwip/apps/sntp.h" #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif @@ -112,6 +113,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { enum dhcp_status dhcp_status = wifi_station_dhcpc_status(); if (!manual_ip.has_value()) { + // lwIP starts the SNTP client if it gets an SNTP server from DHCP. We don't need the time, and more importantly, + // the built-in SNTP client has a memory leak in certain situations. Disable this feature. + // https://github.com/esphome/issues/issues/2299 + sntp_servermode_dhcp(false); + // Use DHCP client if (dhcp_status != DHCP_STARTED) { bool ret = wifi_station_dhcpc_start(); From f0b6aabc964722d3d0da3e49c84cec4125d9e7d4 Mon Sep 17 00:00:00 2001 From: irtimaled Date: Mon, 13 Sep 2021 00:33:20 -0700 Subject: [PATCH 204/285] Support inverting color temperature on tuya lights (#2277) --- esphome/components/tuya/light/__init__.py | 4 ++++ esphome/components/tuya/light/tuya_light.cpp | 9 ++++++++- esphome/components/tuya/light/tuya_light.h | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 979082d636..f43cc570ca 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -18,6 +18,7 @@ DEPENDENCIES = ["tuya"] CONF_DIMMER_DATAPOINT = "dimmer_datapoint" CONF_MIN_VALUE_DATAPOINT = "min_value_datapoint" CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint" +CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert" CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value" TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) @@ -33,6 +34,7 @@ CONFIG_SCHEMA = cv.All( cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" ): cv.uint8_t, + cv.Optional(CONF_COLOR_TEMPERATURE_INVERT, default=False): cv.boolean, cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, cv.Optional(CONF_COLOR_TEMPERATURE_MAX_VALUE): cv.int_, @@ -67,6 +69,8 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_COLOR_TEMPERATURE_DATAPOINT in config: cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT])) + cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT])) + cg.add( var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) ) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 8ad78aacea..6f3adfcdfd 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -9,10 +9,14 @@ static const char *const TAG = "tuya.light"; void TuyaLight::setup() { if (this->color_temperature_id_.has_value()) { this->parent_->register_listener(*this->color_temperature_id_, [this](const TuyaDatapoint &datapoint) { + auto datapoint_value = datapoint.value_uint; + if (this->color_temperature_invert_) { + datapoint_value = this->color_temperature_max_value_ - datapoint_value; + } auto call = this->state_->make_call(); call.set_color_temperature(this->cold_white_temperature_ + (this->warm_white_temperature_ - this->cold_white_temperature_) * - (float(datapoint.value_uint) / float(this->color_temperature_max_value_))); + (float(datapoint_value) / this->color_temperature_max_value_)); call.perform(); }); } @@ -78,6 +82,9 @@ void TuyaLight::write_state(light::LightState *state) { static_cast(this->color_temperature_max_value_ * (state->current_values.get_color_temperature() - this->cold_white_temperature_) / (this->warm_white_temperature_ - this->cold_white_temperature_)); + if (this->color_temperature_invert_) { + color_temp_int = this->color_temperature_max_value_ - color_temp_int; + } parent_->set_integer_datapoint_value(*this->color_temperature_id_, color_temp_int); } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index 72422bc9e7..20753fa90b 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -17,6 +17,9 @@ class TuyaLight : public Component, public light::LightOutput { } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; } + void set_color_temperature_invert(bool color_temperature_invert) { + this->color_temperature_invert_ = color_temperature_invert; + } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } @@ -47,6 +50,7 @@ class TuyaLight : public Component, public light::LightOutput { uint32_t color_temperature_max_value_ = 255; float cold_white_temperature_; float warm_white_temperature_; + bool color_temperature_invert_{false}; light::LightState *state_{nullptr}; }; From f31e0532c4e502de7913f086452a3ac4eebb3473 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:33:29 +0200 Subject: [PATCH 205/285] Untangle core headers (part 1) (#2276) --- esphome/components/ade7953/ade7953.h | 1 + esphome/components/am43/am43_base.cpp | 2 ++ esphome/components/anova/anova_base.cpp | 2 ++ esphome/components/as3935/as3935.h | 1 + esphome/components/climate/climate_traits.cpp | 2 +- esphome/components/deep_sleep/deep_sleep_component.h | 1 + esphome/components/dht/dht.h | 1 + esphome/components/esp32_ble/ble_uuid.h | 1 + .../gpio/binary_sensor/gpio_binary_sensor.h | 1 + esphome/components/gpio/switch/gpio_switch.h | 1 + esphome/components/nfc/nfc.cpp | 1 + esphome/components/rc522/rc522.h | 1 + .../remote_receiver/remote_receiver_esp8266.cpp | 1 + esphome/components/sx1509/sx1509.h | 1 + esphome/components/ttp229_bsf/ttp229_bsf.h | 1 + esphome/components/tx20/tx20.h | 1 + esphome/components/vl53l0x/vl53l0x_sensor.h | 1 + esphome/core/esphal.h | 4 +--- esphome/core/helpers.h | 5 ++++- esphome/core/log.h | 12 +++++++----- esphome/core/preferences.h | 2 +- 21 files changed, 32 insertions(+), 11 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index e0fadf37c3..5a582a991a 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/sensor/sensor.h" diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp index a17df55571..af474dcb79 100644 --- a/esphome/components/am43/am43_base.cpp +++ b/esphome/components/am43/am43_base.cpp @@ -1,4 +1,6 @@ #include "am43_base.h" +#include +#include namespace esphome { namespace am43 { diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index ad581b1e6c..811a34a27a 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -1,4 +1,6 @@ #include "anova_base.h" +#include +#include namespace esphome { namespace anova { diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index d0e53e7832..76c7697d38 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index c871552360..16c9cd05be 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -1,5 +1,5 @@ #include "climate_traits.h" -#include "esphome/core/log.h" +#include namespace esphome { namespace climate { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index e163cf7709..5050bd6b4d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/automation.h" +#include "esphome/core/esphal.h" namespace esphome { namespace deep_sleep { diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 8826a3d2b7..9b848cb119 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 0e3c9b0d0a..89082e19fa 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/helpers.h" +#include "esphome/core/esphal.h" #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index cfe49b3c94..f655207243 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/gpio/switch/gpio_switch.h b/esphome/components/gpio/switch/gpio_switch.h index c0036b20e9..b39e070c85 100644 --- a/esphome/components/gpio/switch/gpio_switch.h +++ b/esphome/components/gpio/switch/gpio_switch.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/switch/switch.h" namespace esphome { diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 01f63e6ec9..706c09a5aa 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -1,4 +1,5 @@ #include "nfc.h" +#include #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index 6880651ac4..e4e3387ff6 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index 3fb68caca2..d509eea925 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -1,4 +1,5 @@ #include "remote_receiver.h" +#include "esphome/core/esphal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 55d5e54091..1390059675 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -2,6 +2,7 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "sx1509_gpio_pin.h" #include "sx1509_registers.h" diff --git a/esphome/components/ttp229_bsf/ttp229_bsf.h b/esphome/components/ttp229_bsf/ttp229_bsf.h index 94dd014d8e..d73e4fb185 100644 --- a/esphome/components/ttp229_bsf/ttp229_bsf.h +++ b/esphome/components/ttp229_bsf/ttp229_bsf.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 8b79deffbc..56cb723fa1 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index 0c37df67a2..83a6322dcd 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -3,6 +3,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h index 809c7d91b5..08fbfb09f5 100644 --- a/esphome/core/esphal.h +++ b/esphome/core/esphal.h @@ -1,9 +1,7 @@ #pragma once #include "Arduino.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif + // Fix some arduino defs #ifdef round #undef round diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 60bc7a9ad3..a63c627e5a 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -6,8 +6,11 @@ #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-psram.h" +#endif + #include "esphome/core/optional.h" -#include "esphome/core/esphal.h" #ifdef CLANG_TIDY #undef ICACHE_RAM_ATTR diff --git a/esphome/core/log.h b/esphome/core/log.h index fbaaf14408..0b0911c3ac 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -3,18 +3,20 @@ #include #include #include + #ifdef USE_STORE_LOG_STR_IN_FLASH #include "WString.h" #endif -#include "esphome/core/macros.h" -// avoid esp-idf redefining our macros -#include "esphome/core/esphal.h" - +// Both the ESP-IDF and Arduino also define ESP_LOG* macros. Include them here, so that they won't +// be reincluded later on and redefine our macros. #ifdef ARDUINO_ARCH_ESP32 -#include "esp_err.h" +#include +#include #endif +#include "esphome/core/macros.h" + namespace esphome { #define ESPHOME_LOG_LEVEL_NONE 0 diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 43d0f023e3..699871ca00 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,8 +1,8 @@ #pragma once #include +#include -#include "esphome/core/esphal.h" #include "esphome/core/defines.h" namespace esphome { From 0cd24c629a727914711c7e9e257713d079a0245a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:35:55 +0200 Subject: [PATCH 206/285] Compatibility with clang-tidy v14 (#2272) --- .clang-tidy | 4 ++++ esphome/components/bme280/bme280.cpp | 20 +++++++++---------- esphome/components/bmp280/bmp280.cpp | 10 +++++----- esphome/components/display/display_buffer.cpp | 4 +--- esphome/components/i2c/i2c.cpp | 2 +- esphome/components/mcp3008/mcp3008.cpp | 3 +-- esphome/components/mqtt/mqtt_client.cpp | 2 +- .../components/remote_base/rc5_protocol.cpp | 6 +++--- esphome/components/st7735/st7735.cpp | 17 ++++++++-------- esphome/components/sun/sun.cpp | 6 ++---- esphome/core/component.cpp | 6 ++---- esphome/core/preferences.cpp | 6 +++--- 12 files changed, 41 insertions(+), 45 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 473529a9b1..936100e9e6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,9 +2,11 @@ Checks: >- *, -abseil-*, + -altera-*, -android-*, -boost-*, -bugprone-branch-clone, + -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, -bugprone-too-small-loop-variable, @@ -20,6 +22,7 @@ Checks: >- -clang-diagnostic-sign-compare, -clang-diagnostic-unused-variable, -clang-diagnostic-unused-const-variable, + -concurrency-*, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, @@ -72,6 +75,7 @@ Checks: >- -readability-const-return-type, -readability-convert-member-functions-to-static, -readability-else-after-return, + -readability-function-cognitive-complexity, -readability-implicit-bool-conversion, -readability-isolate-declaration, -readability-magic-numbers, diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index 097974da7a..fe11b69945 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -113,14 +113,14 @@ void BME280Component::setup() { this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4); this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6); - uint8_t humid_register = 0; - if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) { + uint8_t humid_control_val = 0; + if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) { this->mark_failed(); return; } - humid_register &= ~0b00000111; - humid_register |= this->humidity_oversampling_ & 0b111; - if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) { + humid_control_val &= ~0b00000111; + humid_control_val |= this->humidity_oversampling_ & 0b111; + if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) { this->mark_failed(); return; } @@ -169,11 +169,11 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return ( void BME280Component::update() { // Enable sensor ESP_LOGV(TAG, "Sending conversion request..."); - uint8_t meas_register = 0; - meas_register |= (this->temperature_oversampling_ & 0b111) << 5; - meas_register |= (this->pressure_oversampling_ & 0b111) << 2; - meas_register |= BME280_MODE_FORCED; - if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) { + uint8_t meas_value = 0; + meas_value |= (this->temperature_oversampling_ & 0b111) << 5; + meas_value |= (this->pressure_oversampling_ & 0b111) << 2; + meas_value |= BME280_MODE_FORCED; + if (!this->write_byte(BME280_REGISTER_CONTROL, meas_value)) { this->status_set_warning(); return; } diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index 4048e40077..d04a357ca2 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -123,11 +123,11 @@ inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return ( void BMP280Component::update() { // Enable sensor ESP_LOGV(TAG, "Sending conversion request..."); - uint8_t meas_register = 0; - meas_register |= (this->temperature_oversampling_ & 0b111) << 5; - meas_register |= (this->pressure_oversampling_ & 0b111) << 2; - meas_register |= 0b01; // Forced mode - if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_register)) { + uint8_t meas_value = 0; + meas_value |= (this->temperature_oversampling_ & 0b111) << 5; + meas_value |= (this->pressure_oversampling_ & 0b111) << 2; + meas_value |= 0b01; // Forced mode + if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) { this->status_set_warning(); return; } diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 29f86dbd5f..c330d00ce5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -514,9 +514,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { return Color(gray | gray << 8 | gray << 16 | gray << 24); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) - : Image(data_start, width, height, type), animation_frame_count_(animation_frame_count) { - current_frame_ = 0; -} + : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {} int Animation::get_animation_frame_count() const { return this->animation_frame_count_; } int Animation::get_current_frame() const { return this->current_frame_; } void Animation::next_frame() { diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 77dfcedc04..f1980e1f1d 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -16,7 +16,7 @@ I2CComponent::I2CComponent() { this->wire_ = new TwoWire(next_i2c_bus_num_); next_i2c_bus_num_++; #else - this->wire_ = &Wire; + this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) #endif } diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index 909a6f4708..bea24b5c6a 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -38,9 +38,8 @@ float MCP3008::read_data(uint8_t pin) { } MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, const std::string &name, uint8_t pin, float reference_voltage) - : PollingComponent(1000), parent_(parent), pin_(pin) { + : PollingComponent(1000), parent_(parent), pin_(pin), reference_voltage_(reference_voltage) { this->set_name(name); - this->reference_voltage_ = reference_voltage; } float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index afb67d36ed..be2176df09 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -25,7 +25,7 @@ MQTTClientComponent::MQTTClientComponent() { // Connection void MQTTClientComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up MQTT..."); - this->mqtt_client_.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, + this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { if (index == 0) this->payload_buffer_.reserve(total); diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index 80af2f9ad8..a8b608ec9e 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -41,7 +41,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { .address = 0, .command = 0, }; - int field_bit = 0; + uint8_t field_bit; if (src.expect_space(BIT_TIME_US) && src.expect_mark(BIT_TIME_US)) { field_bit = 1; @@ -60,7 +60,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { return {}; } - uint64_t out_data = 0; + uint32_t out_data = 0; for (int bit = NBITS - 4; bit >= 1; bit--) { if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { @@ -78,7 +78,7 @@ optional RC5Protocol::decode(RemoteReceiveData src) { out_data |= 1; } - out.command = (out_data & 0x3F) + (1 - field_bit) * 64; + out.command = (uint8_t)(out_data & 0x3F) + (1 - field_bit) * 64u; out.address = (out_data >> 6) & 0x1F; return out; } diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 0467ed83db..ac8802739b 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -220,15 +220,14 @@ static const uint8_t PROGMEM // clang-format on static const char *const TAG = "st7735"; -ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) { - model_ = model; - this->width_ = width; - this->height_ = height; - this->colstart_ = colstart; - this->rowstart_ = rowstart; - this->eightbitcolor_ = eightbitcolor; - this->usebgr_ = usebgr; -} +ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) + : model_(model), + colstart_(colstart), + rowstart_(rowstart), + eightbitcolor_(eightbitcolor), + usebgr_(usebgr), + width_(width), + height_(height) {} void ST7735::setup() { ESP_LOGCONFIG(TAG, "Setting up ST7735..."); diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 4fd034ce7d..113c14d431 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -79,10 +79,8 @@ struct SunAtTime { num_t jde; num_t t; - SunAtTime(num_t jde) : jde(jde) { - // eq 25.1, p. 163; julian centuries from the epoch J2000.0 - t = (jde - 2451545) / 36525.0; - } + // eq 25.1, p. 163; julian centuries from the epoch J2000.0 + SunAtTime(num_t jde) : jde(jde), t((jde - 2451545) / 36525.0) {} num_t mean_obliquity() const { // eq. 22.2, p. 147; mean obliquity of the ecliptic diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index cd3081998b..7682c083a5 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -196,10 +196,8 @@ uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; } bool Nameable::is_disabled_by_default() const { return this->disabled_by_default_; } void Nameable::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; } -WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) { - component_ = component; - started_ = millis(); -} +WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component) + : started_(millis()), component_(component) {} WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { uint32_t now = millis(); if (now - started_ > 50) { diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp index 68030a2d59..bfc5d9f875 100644 --- a/esphome/core/preferences.cpp +++ b/esphome/core/preferences.cpp @@ -20,7 +20,7 @@ static const char *const TAG = "preferences"; ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {} ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) : offset_(offset), length_words_(length), type_(type) { - this->data_ = new uint32_t[this->length_words_ + 1]; + this->data_ = new uint32_t[this->length_words_ + 1]; // NOLINT(cppcoreguidelines-prefer-member-initializer) for (uint32_t i = 0; i < this->length_words_ + 1; i++) this->data_[i] = 0; } @@ -69,7 +69,7 @@ static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { return false; } - *dest = ESP_RTC_USER_MEM[index]; + *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) return true; } @@ -83,7 +83,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { return false; } - auto *ptr = &ESP_RTC_USER_MEM[index]; + auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) *ptr = value; return true; } From 3aa107142bdca8a9d1b54ea9073033dfcfaf99d6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:37:11 +0200 Subject: [PATCH 207/285] Only try compat parsing after regular parsing fails (#2269) --- esphome/__main__.py | 146 +++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 68 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 8d6f2b8f89..97b2988c2e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -483,75 +483,9 @@ def parse_args(argv): metavar=("key", "value"), ) - # Keep backward compatibility with the old command line format of - # esphome . - # - # Unfortunately this can't be done by adding another configuration argument to the - # main config parser, as argparse is greedy when parsing arguments, so in regular - # usage it'll eat the command as the configuration argument and error out out - # because it can't parse the configuration as a command. - # - # Instead, construct an ad-hoc parser for the old format that doesn't actually - # process the arguments, but parses them enough to let us figure out if the old - # format is used. In that case, swap the command and configuration in the arguments - # and continue on with the normal parser (after raising a deprecation warning). - # - # Disable argparse's built-in help option and add it manually to prevent this - # parser from printing the help messagefor the old format when invoked with -h. - compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) - compat_parser.add_argument("-h", "--help") - compat_parser.add_argument("configuration", nargs="*") - compat_parser.add_argument( - "command", - choices=[ - "config", - "compile", - "upload", - "logs", - "run", - "clean-mqtt", - "wizard", - "mqtt-fingerprint", - "version", - "clean", - "dashboard", - "vscode", - "update-all", - ], - ) - - # on Python 3.9+ we can simply set exit_on_error=False in the constructor - def _raise(x): - raise argparse.ArgumentError(None, x) - - compat_parser.error = _raise - - deprecated_argv_suggestion = None - - if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]: - # this is most likely meant in new-style arg format. do not try compat parsing - pass - else: - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - unparsed = [ - "--device" if arg in ("--upload-port", "--serial-port") else arg - for arg in unparsed - ] - argv = ( - argv[0:last_option] + [result.command] + result.configuration + unparsed - ) - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - pass - - # And continue on with regular parsing parser = argparse.ArgumentParser( description=f"ESPHome v{const.__version__}", parents=[options_parser] ) - parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) mqtt_options = argparse.ArgumentParser(add_help=False) mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.") @@ -701,7 +635,83 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) - return parser.parse_args(argv[1:]) + # Keep backward compatibility with the old command line format of + # esphome . + # + # Unfortunately this can't be done by adding another configuration argument to the + # main config parser, as argparse is greedy when parsing arguments, so in regular + # usage it'll eat the command as the configuration argument and error out out + # because it can't parse the configuration as a command. + # + # Instead, if parsing using the current format fails, construct an ad-hoc parser + # that doesn't actually process the arguments, but parses them enough to let us + # figure out if the old format is used. In that case, swap the command and + # configuration in the arguments and retry with the normal parser (and raise + # a deprecation warning). + arguments = argv[1:] + + # On Python 3.9+ we can simply set exit_on_error=False in the constructor + def _raise(x): + raise argparse.ArgumentError(None, x) + + # First, try new-style parsing, but don't exit in case of failure + try: + # duplicate parser so that we can use the original one to raise errors later on + current_parser = argparse.ArgumentParser(add_help=False, parents=[parser]) + current_parser.set_defaults(deprecated_argv_suggestion=None) + current_parser.error = _raise + return current_parser.parse_args(arguments) + except argparse.ArgumentError: + pass + + # Second, try compat parsing and rearrange the command-line if it succeeds + # Disable argparse's built-in help option and add it manually to prevent this + # parser from printing the help messagefor the old format when invoked with -h. + compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False) + compat_parser.add_argument("-h", "--help", action="store_true") + compat_parser.add_argument("configuration", nargs="*") + compat_parser.add_argument( + "command", + choices=[ + "config", + "compile", + "upload", + "logs", + "run", + "clean-mqtt", + "wizard", + "mqtt-fingerprint", + "version", + "clean", + "dashboard", + "vscode", + "update-all", + ], + ) + + try: + compat_parser.error = _raise + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + arguments = ( + arguments[0:last_option] + + [result.command] + + result.configuration + + unparsed + ) + deprecated_argv_suggestion = arguments + except argparse.ArgumentError: + # old-style parsing failed, don't suggest any argument + deprecated_argv_suggestion = None + + # Finally, run the new-style parser again with the possibly swapped arguments, + # and let it error out if the command is unparsable. + parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion) + return parser.parse_args(arguments) def run_esphome(argv): @@ -715,7 +725,7 @@ def run_esphome(argv): "and will be removed in the future. " ) _LOGGER.warning("Please instead use:") - _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:])) + _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) if sys.version_info < (3, 7, 0): _LOGGER.error( From e18dfdd6569a22ef16c5439b8e29404f4e2dc62b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:39:18 +0200 Subject: [PATCH 208/285] Suppress excessive warnings about deprecated Fan interfaces (#2270) --- esphome/components/api/api_connection.cpp | 5 ++++- esphome/components/fan/fan_helpers.cpp | 7 ++++--- esphome/components/fan/fan_helpers.h | 8 ++++++++ esphome/components/fan/fan_state.cpp | 2 ++ esphome/components/mqtt/mqtt_fan.cpp | 1 + esphome/components/web_server/web_server.cpp | 1 + 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 650f4f6f6e..ea3be42275 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -214,6 +214,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #endif #ifdef USE_FAN +// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool APIConnection::send_fan_state(fan::FanState *fan) { if (!this->state_subscription_) return false; @@ -262,13 +265,13 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { // Prefer level call.set_speed(msg.speed_level); } else if (msg.has_speed) { - // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); } +#pragma GCC diagnostic pop #endif #ifdef USE_LIGHT diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp index 5d923a1b15..34883617e6 100644 --- a/esphome/components/fan/fan_helpers.cpp +++ b/esphome/components/fan/fan_helpers.cpp @@ -4,14 +4,15 @@ namespace esphome { namespace fan { -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) +// This whole file is deprecated, don't warn about usage of deprecated types in here. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); // NOLINT(clang-diagnostic-deprecated-declarations) + return static_cast(legacy_level - 1); } -// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { const auto enum_level = static_cast(speed) + 1; const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h index 138aa5bca3..009505601e 100644 --- a/esphome/components/fan/fan_helpers.h +++ b/esphome/components/fan/fan_helpers.h @@ -4,8 +4,16 @@ namespace esphome { namespace fan { +// Shut-up about usage of deprecated FanSpeed for a bit. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9") FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); +ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9") int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); +#pragma GCC diagnostic pop + } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index a4883c5e2c..a57115beb4 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -67,6 +67,8 @@ void FanStateCall::perform() const { this->state_->state_callback_.call(); } +// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); if (strcasecmp(legacy_speed, "low") == 0) { diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index ba9121bc5d..b8eecf0ff3 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -100,6 +100,7 @@ bool MQTTFanComponent::publish_state() { auto traits = this->state_->get_traits(); if (traits.supports_speed()) { const char *payload; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) payload = "low"; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 56c75a1c58..dc97bcd5c2 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -397,6 +397,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; + // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) root["speed"] = "low"; From d594a6fcbca53472b58649fcc13d897fb3edd3e4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 09:48:52 +0200 Subject: [PATCH 209/285] Store strings only used for logging in flash (#2274) Co-authored-by: Otto winter --- .../components/binary_sensor/binary_sensor.h | 2 +- esphome/components/climate/climate.cpp | 44 +++++---- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_mode.cpp | 98 +++++++++---------- esphome/components/climate/climate_mode.h | 11 ++- esphome/components/cover/cover.h | 2 +- .../components/gpio/switch/gpio_switch.cpp | 16 +-- .../hitachi_ac344/hitachi_ac344.cpp | 2 +- .../hitachi_ac424/hitachi_ac424.cpp | 2 +- esphome/components/light/light_call.cpp | 33 ++++--- esphome/components/mqtt/mqtt_client.cpp | 24 ++--- esphome/components/number/number.h | 2 +- esphome/components/select/select.h | 2 +- esphome/components/sensor/sensor.cpp | 8 +- esphome/components/sensor/sensor.h | 7 +- esphome/components/switch/switch.h | 2 +- esphome/components/text_sensor/text_sensor.h | 2 +- esphome/components/uart/uart.cpp | 14 +-- esphome/components/uart/uart.h | 3 +- esphome/components/uart/uart_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 67 ++++++------- .../wifi/wifi_component_esp8266.cpp | 38 +++---- esphome/core/esphal.cpp | 69 +++++-------- esphome/core/esphal.h | 7 +- esphome/core/log.h | 33 ++++--- 25 files changed, 241 insertions(+), 251 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 62e8031cb5..195badd798 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -10,7 +10,7 @@ namespace binary_sensor { #define LOG_BINARY_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 4861e7b8cb..383103b014 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -9,8 +9,8 @@ void ClimateCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); if (this->mode_.has_value()) { - const char *mode_s = climate_mode_to_string(*this->mode_); - ESP_LOGD(TAG, " Mode: %s", mode_s); + const LogString *mode_s = climate_mode_to_string(*this->mode_); + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); } if (this->custom_fan_mode_.has_value()) { this->fan_mode_.reset(); @@ -18,8 +18,8 @@ void ClimateCall::perform() { } if (this->fan_mode_.has_value()) { this->custom_fan_mode_.reset(); - const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); - ESP_LOGD(TAG, " Fan: %s", fan_mode_s); + const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); + ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); } if (this->custom_preset_.has_value()) { this->preset_.reset(); @@ -27,12 +27,12 @@ void ClimateCall::perform() { } if (this->preset_.has_value()) { this->custom_preset_.reset(); - const char *preset_s = climate_preset_to_string(*this->preset_); - ESP_LOGD(TAG, " Preset: %s", preset_s); + const LogString *preset_s = climate_preset_to_string(*this->preset_); + ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); } if (this->swing_mode_.has_value()) { - const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); - ESP_LOGD(TAG, " Swing: %s", swing_mode_s); + const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); + ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s)); } if (this->target_temperature_.has_value()) { ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_); @@ -50,7 +50,7 @@ void ClimateCall::validate_() { if (this->mode_.has_value()) { auto mode = *this->mode_; if (!traits.supports_mode(mode)) { - ESP_LOGW(TAG, " Mode %s is not supported by this device!", climate_mode_to_string(mode)); + ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(climate_mode_to_string(mode))); this->mode_.reset(); } } @@ -63,7 +63,8 @@ void ClimateCall::validate_() { } else if (this->fan_mode_.has_value()) { auto fan_mode = *this->fan_mode_; if (!traits.supports_fan_mode(fan_mode)) { - ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode)); + ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", + LOG_STR_ARG(climate_fan_mode_to_string(fan_mode))); this->fan_mode_.reset(); } } @@ -76,14 +77,15 @@ void ClimateCall::validate_() { } else if (this->preset_.has_value()) { auto preset = *this->preset_; if (!traits.supports_preset(preset)) { - ESP_LOGW(TAG, " Preset %s is not supported by this device!", climate_preset_to_string(preset)); + ESP_LOGW(TAG, " Preset %s is not supported by this device!", LOG_STR_ARG(climate_preset_to_string(preset))); this->preset_.reset(); } } if (this->swing_mode_.has_value()) { auto swing_mode = *this->swing_mode_; if (!traits.supports_swing_mode(swing_mode)) { - ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode)); + ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", + LOG_STR_ARG(climate_swing_mode_to_string(swing_mode))); this->swing_mode_.reset(); } } @@ -373,24 +375,24 @@ void Climate::publish_state() { ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); auto traits = this->get_traits(); - ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); if (traits.get_supports_action()) { - ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action)); + ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action))); } if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) { - ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode.value())); + ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); } if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) { ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str()); } if (traits.get_supports_presets() && this->preset.has_value()) { - ESP_LOGD(TAG, " Preset: %s", climate_preset_to_string(this->preset.value())); + ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); } if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) { ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str()); } if (traits.get_supports_swing_modes()) { - ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode)); + ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); } if (traits.get_supports_current_temperature()) { ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature); @@ -534,12 +536,12 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported modes:"); for (ClimateMode m : traits.get_supported_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m))); } if (!traits.get_supported_fan_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported fan modes:"); for (ClimateFanMode m : traits.get_supported_fan_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:"); @@ -549,7 +551,7 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_presets().empty()) { ESP_LOGCONFIG(tag, " [x] Supported presets:"); for (ClimatePreset p : traits.get_supported_presets()) - ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p))); } if (!traits.get_supported_custom_presets().empty()) { ESP_LOGCONFIG(tag, " [x] Supported custom presets:"); @@ -559,7 +561,7 @@ void Climate::dump_traits_(const char *tag) { if (!traits.get_supported_swing_modes().empty()) { ESP_LOGCONFIG(tag, " [x] Supported swing modes:"); for (ClimateSwingMode m : traits.get_supported_swing_modes()) - ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m)); + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m))); } } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 46d0fb1d77..418db02485 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -12,7 +12,7 @@ namespace climate { #define LOG_CLIMATE(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ } class Climate; diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 7a626942eb..e46159a750 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -3,105 +3,105 @@ namespace esphome { namespace climate { -const char *climate_mode_to_string(ClimateMode mode) { +const LogString *climate_mode_to_string(ClimateMode mode) { switch (mode) { case CLIMATE_MODE_OFF: - return "OFF"; - case CLIMATE_MODE_AUTO: - return "AUTO"; - case CLIMATE_MODE_COOL: - return "COOL"; - case CLIMATE_MODE_HEAT: - return "HEAT"; - case CLIMATE_MODE_FAN_ONLY: - return "FAN_ONLY"; - case CLIMATE_MODE_DRY: - return "DRY"; + return LOG_STR("OFF"); case CLIMATE_MODE_HEAT_COOL: - return "HEAT_COOL"; + return LOG_STR("HEAT_COOL"); + case CLIMATE_MODE_AUTO: + return LOG_STR("AUTO"); + case CLIMATE_MODE_COOL: + return LOG_STR("COOL"); + case CLIMATE_MODE_HEAT: + return LOG_STR("HEAT"); + case CLIMATE_MODE_FAN_ONLY: + return LOG_STR("FAN_ONLY"); + case CLIMATE_MODE_DRY: + return LOG_STR("DRY"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_action_to_string(ClimateAction action) { +const LogString *climate_action_to_string(ClimateAction action) { switch (action) { case CLIMATE_ACTION_OFF: - return "OFF"; + return LOG_STR("OFF"); case CLIMATE_ACTION_COOLING: - return "COOLING"; + return LOG_STR("COOLING"); case CLIMATE_ACTION_HEATING: - return "HEATING"; + return LOG_STR("HEATING"); case CLIMATE_ACTION_IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case CLIMATE_ACTION_DRYING: - return "DRYING"; + return LOG_STR("DRYING"); case CLIMATE_ACTION_FAN: - return "FAN"; + return LOG_STR("FAN"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) { +const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) { switch (fan_mode) { case climate::CLIMATE_FAN_ON: - return "ON"; + return LOG_STR("ON"); case climate::CLIMATE_FAN_OFF: - return "OFF"; + return LOG_STR("OFF"); case climate::CLIMATE_FAN_AUTO: - return "AUTO"; + return LOG_STR("AUTO"); case climate::CLIMATE_FAN_LOW: - return "LOW"; + return LOG_STR("LOW"); case climate::CLIMATE_FAN_MEDIUM: - return "MEDIUM"; + return LOG_STR("MEDIUM"); case climate::CLIMATE_FAN_HIGH: - return "HIGH"; + return LOG_STR("HIGH"); case climate::CLIMATE_FAN_MIDDLE: - return "MIDDLE"; + return LOG_STR("MIDDLE"); case climate::CLIMATE_FAN_FOCUS: - return "FOCUS"; + return LOG_STR("FOCUS"); case climate::CLIMATE_FAN_DIFFUSE: - return "DIFFUSE"; + return LOG_STR("DIFFUSE"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { +const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { switch (swing_mode) { case climate::CLIMATE_SWING_OFF: - return "OFF"; + return LOG_STR("OFF"); case climate::CLIMATE_SWING_BOTH: - return "BOTH"; + return LOG_STR("BOTH"); case climate::CLIMATE_SWING_VERTICAL: - return "VERTICAL"; + return LOG_STR("VERTICAL"); case climate::CLIMATE_SWING_HORIZONTAL: - return "HORIZONTAL"; + return LOG_STR("HORIZONTAL"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -const char *climate_preset_to_string(ClimatePreset preset) { +const LogString *climate_preset_to_string(ClimatePreset preset) { switch (preset) { case climate::CLIMATE_PRESET_NONE: - return "NONE"; + return LOG_STR("NONE"); case climate::CLIMATE_PRESET_HOME: - return "HOME"; + return LOG_STR("HOME"); case climate::CLIMATE_PRESET_ECO: - return "ECO"; + return LOG_STR("ECO"); case climate::CLIMATE_PRESET_AWAY: - return "AWAY"; + return LOG_STR("AWAY"); case climate::CLIMATE_PRESET_BOOST: - return "BOOST"; + return LOG_STR("BOOST"); case climate::CLIMATE_PRESET_COMFORT: - return "COMFORT"; + return LOG_STR("COMFORT"); case climate::CLIMATE_PRESET_SLEEP: - return "SLEEP"; + return LOG_STR("SLEEP"); case climate::CLIMATE_PRESET_ACTIVITY: - return "ACTIVITY"; + return LOG_STR("ACTIVITY"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 476cf5bd84..3e5626919c 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -1,6 +1,7 @@ #pragma once #include +#include "esphome/core/log.h" namespace esphome { namespace climate { @@ -96,19 +97,19 @@ enum ClimatePreset : uint8_t { }; /// Convert the given ClimateMode to a human-readable string. -const char *climate_mode_to_string(ClimateMode mode); +const LogString *climate_mode_to_string(ClimateMode mode); /// Convert the given ClimateAction to a human-readable string. -const char *climate_action_to_string(ClimateAction action); +const LogString *climate_action_to_string(ClimateAction action); /// Convert the given ClimateFanMode to a human-readable string. -const char *climate_fan_mode_to_string(ClimateFanMode mode); +const LogString *climate_fan_mode_to_string(ClimateFanMode mode); /// Convert the given ClimateSwingMode to a human-readable string. -const char *climate_swing_mode_to_string(ClimateSwingMode mode); +const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); /// Convert the given ClimateSwingMode to a human-readable string. -const char *climate_preset_to_string(ClimatePreset preset); +const LogString *climate_preset_to_string(ClimatePreset preset); } // namespace climate } // namespace esphome diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 77a53f11c5..72ec15a459 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -13,7 +13,7 @@ const extern float COVER_CLOSED; #define LOG_COVER(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ auto traits_ = (obj)->get_traits(); \ if (traits_.get_is_assumed_state()) { \ ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \ diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 410a3818d6..56e0087eae 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -47,28 +47,28 @@ void GPIOSwitch::setup() { void GPIOSwitch::dump_config() { LOG_SWITCH("", "GPIO Switch", this); LOG_PIN(" Pin: ", this->pin_); - const char *restore_mode = ""; + const LogString *restore_mode = LOG_STR(""); switch (this->restore_mode_) { case GPIO_SWITCH_RESTORE_DEFAULT_OFF: - restore_mode = "Restore (Defaults to OFF)"; + restore_mode = LOG_STR("Restore (Defaults to OFF)"); break; case GPIO_SWITCH_RESTORE_DEFAULT_ON: - restore_mode = "Restore (Defaults to ON)"; + restore_mode = LOG_STR("Restore (Defaults to ON)"); break; case GPIO_SWITCH_RESTORE_INVERTED_DEFAULT_ON: - restore_mode = "Restore inverted (Defaults to ON)"; + restore_mode = LOG_STR("Restore inverted (Defaults to ON)"); break; case GPIO_SWITCH_RESTORE_INVERTED_DEFAULT_OFF: - restore_mode = "Restore inverted (Defaults to OFF)"; + restore_mode = LOG_STR("Restore inverted (Defaults to OFF)"); break; case GPIO_SWITCH_ALWAYS_OFF: - restore_mode = "Always OFF"; + restore_mode = LOG_STR("Always OFF"); break; case GPIO_SWITCH_ALWAYS_ON: - restore_mode = "Always ON"; + restore_mode = LOG_STR("Always ON"); break; } - ESP_LOGCONFIG(TAG, " Restore Mode: %s", restore_mode); + ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); if (!this->interlock_.empty()) { ESP_LOGCONFIG(TAG, " Interlocks:"); for (auto *lock : this->interlock_) { diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 1e5bca1396..067ea39d07 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -165,7 +165,7 @@ void HitachiClimate::transmit_state() { set_power_(false); break; default: - ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); } set_temp_(static_cast(this->target_temperature)); diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 10b83cbd58..2e5423a37a 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -166,7 +166,7 @@ void HitachiClimate::transmit_state() { set_power_(false); break; default: - ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); } set_temp_(static_cast(this->target_temperature)); diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index d979b13368..178a78a94c 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -7,24 +7,24 @@ namespace light { static const char *const TAG = "light"; -static const char *color_mode_to_human(ColorMode color_mode) { +static const LogString *color_mode_to_human(ColorMode color_mode) { if (color_mode == ColorMode::UNKNOWN) - return "Unknown"; + return LOG_STR("Unknown"); if (color_mode == ColorMode::WHITE) - return "White"; + return LOG_STR("White"); if (color_mode == ColorMode::COLOR_TEMPERATURE) - return "Color temperature"; + return LOG_STR("Color temperature"); if (color_mode == ColorMode::COLD_WARM_WHITE) - return "Cold/warm white"; + return LOG_STR("Cold/warm white"); if (color_mode == ColorMode::RGB) - return "RGB"; + return LOG_STR("RGB"); if (color_mode == ColorMode::RGB_WHITE) - return "RGBW"; + return LOG_STR("RGBW"); if (color_mode == ColorMode::RGB_COLD_WARM_WHITE) - return "RGB + cold/warm white"; + return LOG_STR("RGB + cold/warm white"); if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE) - return "RGB + color temperature"; - return ""; + return LOG_STR("RGB + color temperature"); + return LOG_STR(""); } void LightCall::perform() { @@ -37,7 +37,7 @@ void LightCall::perform() { // Only print color mode when it's being changed ColorMode current_color_mode = this->parent_->remote_values.get_color_mode(); if (this->color_mode_.value_or(current_color_mode) != current_color_mode) { - ESP_LOGD(TAG, " Color mode: %s", color_mode_to_human(v.get_color_mode())); + ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode()))); } // Only print state when it's being changed @@ -135,7 +135,7 @@ LightColorValues LightCall::validate_() { // Color mode check if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, - color_mode_to_human(this->color_mode_.value())); + LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); this->color_mode_.reset(); } @@ -206,7 +206,8 @@ LightColorValues LightCall::validate_() { if (name_##_.has_value()) { \ auto val = *name_##_; \ if (val < (min) || val > (max)) { \ - ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, upper_name, val, (min), (max)); \ + ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, LOG_STR_LITERAL(upper_name), val, \ + (min), (max)); \ name_##_ = clamp(val, (min), (max)); \ } \ } @@ -387,7 +388,7 @@ ColorMode LightCall::compute_color_mode_() { // Don't change if the current mode is suitable. if (suitable_modes.count(current_mode) > 0) { ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", - this->parent_->get_name().c_str(), color_mode_to_human(current_mode)); + this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(current_mode))); return current_mode; } @@ -397,7 +398,7 @@ ColorMode LightCall::compute_color_mode_() { continue; ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), - color_mode_to_human(mode)); + LOG_STR_ARG(color_mode_to_human(mode))); return mode; } @@ -405,7 +406,7 @@ ColorMode LightCall::compute_color_mode_() { // out whatever we don't support. auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", - this->parent_->get_name().c_str(), color_mode_to_human(color_mode)); + this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(color_mode))); return color_mode; } std::set LightCall::get_suitable_color_modes_() { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index be2176df09..129597cc00 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -221,40 +221,40 @@ void MQTTClientComponent::check_connected() { void MQTTClientComponent::loop() { if (this->disconnect_reason_.has_value()) { - const char *reason_s = nullptr; + const LogString *reason_s; switch (*this->disconnect_reason_) { case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED: - reason_s = "TCP disconnected"; + reason_s = LOG_STR("TCP disconnected"); break; case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: - reason_s = "Unacceptable Protocol Version"; + reason_s = LOG_STR("Unacceptable Protocol Version"); break; case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: - reason_s = "Identifier Rejected"; + reason_s = LOG_STR("Identifier Rejected"); break; case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: - reason_s = "Server Unavailable"; + reason_s = LOG_STR("Server Unavailable"); break; case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: - reason_s = "Malformed Credentials"; + reason_s = LOG_STR("Malformed Credentials"); break; case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED: - reason_s = "Not Authorized"; + reason_s = LOG_STR("Not Authorized"); break; case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: - reason_s = "Not Enough Space"; + reason_s = LOG_STR("Not Enough Space"); break; case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT: - reason_s = "TLS Bad Fingerprint"; + reason_s = LOG_STR("TLS Bad Fingerprint"); break; default: - reason_s = "Unknown"; + reason_s = LOG_STR("Unknown"); break; } if (!network_is_connected()) { - reason_s = "WiFi disconnected"; + reason_s = LOG_STR("WiFi disconnected"); } - ESP_LOGW(TAG, "MQTT Disconnected: %s.", reason_s); + ESP_LOGW(TAG, "MQTT Disconnected: %s.", LOG_STR_ARG(reason_s)); this->disconnect_reason_.reset(); } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index e32b53187b..945f174510 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -8,7 +8,7 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->traits.get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 414a8daabb..0dec74b627 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -10,7 +10,7 @@ namespace select { #define LOG_SELECT(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->traits.get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 1dbc1c901a..34f72a4508 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,15 +6,15 @@ namespace sensor { static const char *const TAG = "sensor"; -const char *state_class_to_string(StateClass state_class) { +const LogString *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: - return "measurement"; + return LOG_STR("measurement"); case STATE_CLASS_TOTAL_INCREASING: - return "total_increasing"; + return LOG_STR("total_increasing"); case STATE_CLASS_NONE: default: - return ""; + return LOG_STR(""); } } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 34b8b26a54..6322c12027 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/log.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/filter.h" @@ -9,11 +10,11 @@ namespace sensor { #define LOG_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->state_class))); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -36,7 +37,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const char *state_class_to_string(StateClass state_class); +const LogString *state_class_to_string(StateClass state_class); /** Base-class for all sensors. * diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 966119a29f..8cfae3b6f8 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -9,7 +9,7 @@ namespace switch_ { #define LOG_SWITCH(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 5c6e5be51a..5293f0d216 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -8,7 +8,7 @@ namespace text_sensor { #define LOG_TEXT_SENSOR(prefix, type, obj) \ if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ if (!(obj)->get_icon().empty()) { \ ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ } \ diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 08e3395a7a..8cc8a47b14 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -58,21 +58,21 @@ void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UART this->parent_->data_bits_); } if (this->parent_->parity_ != parity) { - ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", parity_to_str(parity), - parity_to_str(this->parent_->parity_)); + ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", + LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->parity_))); } } -const char *parity_to_str(UARTParityOptions parity) { +const LogString *parity_to_str(UARTParityOptions parity) { switch (parity) { case UART_CONFIG_PARITY_NONE: - return "NONE"; + return LOG_STR("NONE"); case UART_CONFIG_PARITY_EVEN: - return "EVEN"; + return LOG_STR("EVEN"); case UART_CONFIG_PARITY_ODD: - return "ODD"; + return LOG_STR("ODD"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 8ac00658f4..041cdaecdf 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -4,6 +4,7 @@ #include #include "esphome/core/esphal.h" #include "esphome/core/component.h" +#include "esphome/core/log.h" namespace esphome { namespace uart { @@ -14,7 +15,7 @@ enum UARTParityOptions { UART_CONFIG_PARITY_ODD, }; -const char *parity_to_str(UARTParityOptions parity); +const LogString *parity_to_str(UARTParityOptions parity); #ifdef ARDUINO_ARCH_ESP8266 class ESP8266SoftwareSerial { diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index 5cb625f2ff..bc6b0f72d3 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -104,7 +104,7 @@ void UARTComponent::dump_config() { } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); - ESP_LOGCONFIG(TAG, " Parity: %s", parity_to_str(this->parity_)); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); if (this->hw_serial_ != nullptr) { ESP_LOGCONFIG(TAG, " Using hardware serial interface."); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 50feeb6cad..c5ba9b01af 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -307,7 +307,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { this->action_started_ = millis(); } -void print_signal_bars(int8_t rssi, char *buf) { +const LogString *get_signal_bars(int8_t rssi) { // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -317,36 +317,36 @@ void print_signal_bars(int8_t rssi, char *buf) { // FULL BLOCK // Unicode: U+2588, UTF-8: E2 96 88 if (rssi >= -50) { - sprintf(buf, "\033[0;32m" // green - "\xe2\x96\x82" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;32m" // green + "\xe2\x96\x82" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } else if (rssi >= -65) { - sprintf(buf, "\033[0;33m" // yellow - "\xe2\x96\x82" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\033[0;37m" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;33m" // yellow + "\xe2\x96\x82" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\033[0;37m" + "\xe2\x96\x88" + "\033[0m"); } else if (rssi >= -85) { - sprintf(buf, "\033[0;33m" // yellow - "\xe2\x96\x82" - "\xe2\x96\x84" - "\033[0;37m" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;33m" // yellow + "\xe2\x96\x82" + "\xe2\x96\x84" + "\033[0;37m" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } else { - sprintf(buf, "\033[0;31m" // red - "\xe2\x96\x82" - "\033[0;37m" - "\xe2\x96\x84" - "\xe2\x96\x86" - "\xe2\x96\x88" - "\033[0m"); + return LOG_STR("\033[0;31m" // red + "\xe2\x96\x82" + "\033[0;37m" + "\xe2\x96\x84" + "\xe2\x96\x86" + "\xe2\x96\x88" + "\033[0m"); } } @@ -361,10 +361,8 @@ void WiFiComponent::print_connect_params_() { ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - char signal_bars[50]; int8_t rssi = WiFi.RSSI(); - print_signal_bars(rssi, signal_bars); - ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, signal_bars); + ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, LOG_STR_ARG(get_signal_bars(rssi))); if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } @@ -433,16 +431,15 @@ void WiFiComponent::check_scanning_finished() { char bssid_s[18]; auto bssid = res.get_bssid(); sprintf(bssid_s, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); - char signal_bars[50]; - print_signal_bars(res.get_rssi(), signal_bars); if (res.get_matches()) { ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), - res.get_is_hidden() ? "(HIDDEN) " : "", bssid_s, signal_bars); + res.get_is_hidden() ? "(HIDDEN) " : "", bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); ESP_LOGD(TAG, " Channel: %u", res.get_channel()); ESP_LOGD(TAG, " RSSI: %d dB", res.get_rssi()); } else { - ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, signal_bars); + ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); } } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index de529ee3aa..842b44b705 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -327,20 +327,20 @@ class WiFiMockClass : public ESP8266WiFiGenericClass { static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT }; -const char *get_auth_mode_str(uint8_t mode) { +const LogString *get_auth_mode_str(uint8_t mode) { switch (mode) { case AUTH_OPEN: - return "OPEN"; + return LOG_STR("OPEN"); case AUTH_WEP: - return "WEP"; + return LOG_STR("WEP"); case AUTH_WPA_PSK: - return "WPA PSK"; + return LOG_STR("WPA PSK"); case AUTH_WPA2_PSK: - return "WPA2 PSK"; + return LOG_STR("WPA2 PSK"); case AUTH_WPA_WPA2_PSK: - return "WPA/WPA2 PSK"; + return LOG_STR("WPA/WPA2 PSK"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } #ifdef ipv4_addr @@ -358,22 +358,22 @@ std::string format_ip_addr(struct ip_addr ip) { return buf; } #endif -const char *get_op_mode_str(uint8_t mode) { +const LogString *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: - return "OFF"; + return LOG_STR("OFF"); case WIFI_STA: - return "STA"; + return LOG_STR("STA"); case WIFI_AP: - return "AP"; + return LOG_STR("AP"); case WIFI_AP_STA: - return "AP+STA"; + return LOG_STR("AP+STA"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -// Note that this method returns PROGMEM strings, so use LOG_STR_ARG() to access them. -const char *get_disconnect_reason_str(uint8_t reason) { + +const LogString *get_disconnect_reason_str(uint8_t reason) { /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the * REASON_* constants aren't continuous, and GCC will fill in the gap with the default value -- wasting 4 bytes of RAM * per entry. As there's ~175 default entries, this wastes 700 bytes of RAM. @@ -470,8 +470,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_AUTHMODE_CHANGE: { auto it = event->event_info.auth_change; - ESP_LOGV(TAG, "Event: Changed AuthMode old=%s new=%s", get_auth_mode_str(it.old_mode), - get_auth_mode_str(it.new_mode)); + ESP_LOGV(TAG, "Event: Changed AuthMode old=%s new=%s", LOG_STR_ARG(get_auth_mode_str(it.old_mode)), + LOG_STR_ARG(get_auth_mode_str(it.new_mode))); // Mitigate CVE-2020-12638 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors if (it.old_mode != AUTH_OPEN && it.new_mode == AUTH_OPEN) { @@ -511,8 +511,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; - ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", get_op_mode_str(it.old_opmode), - get_op_mode_str(it.new_opmode)); + ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)), + LOG_STR_ARG(get_op_mode_str(it.new_opmode))); break; } case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 7851ba01b6..8fb1dd86c5 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -43,81 +43,56 @@ GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) { } -const char *GPIOPin::get_pin_mode_name() const { +const LogString *GPIOPin::get_pin_mode_name() const { const char *mode_s; switch (this->mode_) { case INPUT: - mode_s = "INPUT"; - break; + return LOG_STR("INPUT"); case OUTPUT: - mode_s = "OUTPUT"; - break; + return LOG_STR("OUTPUT"); case INPUT_PULLUP: - mode_s = "INPUT_PULLUP"; - break; + return LOG_STR("INPUT_PULLUP"); case OUTPUT_OPEN_DRAIN: - mode_s = "OUTPUT_OPEN_DRAIN"; - break; + return LOG_STR("OUTPUT_OPEN_DRAIN"); case SPECIAL: - mode_s = "SPECIAL"; - break; + return LOG_STR("SPECIAL"); case FUNCTION_1: - mode_s = "FUNCTION_1"; - break; + return LOG_STR("FUNCTION_1"); case FUNCTION_2: - mode_s = "FUNCTION_2"; - break; + return LOG_STR("FUNCTION_2"); case FUNCTION_3: - mode_s = "FUNCTION_3"; - break; + return LOG_STR("FUNCTION_3"); case FUNCTION_4: - mode_s = "FUNCTION_4"; - break; - + return LOG_STR("FUNCTION_4"); #ifdef ARDUINO_ARCH_ESP32 case PULLUP: - mode_s = "PULLUP"; - break; + return LOG_STR("PULLUP"); case PULLDOWN: - mode_s = "PULLDOWN"; - break; + return LOG_STR("PULLDOWN"); case INPUT_PULLDOWN: - mode_s = "INPUT_PULLDOWN"; - break; + return LOG_STR("INPUT_PULLDOWN"); case OPEN_DRAIN: - mode_s = "OPEN_DRAIN"; - break; + return LOG_STR("OPEN_DRAIN"); case FUNCTION_5: - mode_s = "FUNCTION_5"; - break; + return LOG_STR("FUNCTION_5"); case FUNCTION_6: - mode_s = "FUNCTION_6"; - break; + return LOG_STR("FUNCTION_6"); case ANALOG: - mode_s = "ANALOG"; - break; + return LOG_STR("ANALOG"); #endif #ifdef ARDUINO_ARCH_ESP8266 case FUNCTION_0: - mode_s = "FUNCTION_0"; - break; + return LOG_STR("FUNCTION_0"); case WAKEUP_PULLUP: - mode_s = "WAKEUP_PULLUP"; - break; + return LOG_STR("WAKEUP_PULLUP"); case WAKEUP_PULLDOWN: - mode_s = "WAKEUP_PULLDOWN"; - break; + return LOG_STR("WAKEUP_PULLDOWN"); case INPUT_PULLDOWN_16: - mode_s = "INPUT_PULLDOWN_16"; - break; + return LOG_STR("INPUT_PULLDOWN_16"); #endif - default: - mode_s = "UNKNOWN"; - break; + return LOG_STR("UNKNOWN"); } - - return mode_s; } unsigned char GPIOPin::get_pin() const { return this->pin_; } diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h index 08fbfb09f5..1b92f816b1 100644 --- a/esphome/core/esphal.h +++ b/esphome/core/esphal.h @@ -25,6 +25,8 @@ #undef abs #endif +#include "esphome/core/log.h" + namespace esphome { #define LOG_PIN(prefix, pin) \ @@ -32,7 +34,8 @@ namespace esphome { ESP_LOGCONFIG(TAG, prefix LOG_PIN_PATTERN, LOG_PIN_ARGS(pin)); \ } #define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)" -#define LOG_PIN_ARGS(pin) (pin)->get_pin(), (pin)->get_pin_mode_name(), ((pin)->is_inverted() ? ", INVERTED" : "") +#define LOG_PIN_ARGS(pin) \ + (pin)->get_pin(), LOG_STR_ARG((pin)->get_pin_mode_name()), ((pin)->is_inverted() ? ", INVERTED" : "") /// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) class ISRInternalGPIOPin { @@ -85,7 +88,7 @@ class GPIOPin { /// Get the GPIO pin number. uint8_t get_pin() const; - const char *get_pin_mode_name() const; + const LogString *get_pin_mode_name() const; /// Get the pinMode of this pin. uint8_t get_mode() const; /// Return whether this pin shall be treated as inverted. (for example active-low) diff --git a/esphome/core/log.h b/esphome/core/log.h index 0b0911c3ac..3c2f1e6999 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -165,28 +165,37 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #define ONOFF(b) ((b) ? "ON" : "OFF") #define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") -#ifdef USE_STORE_LOG_STR_IN_FLASH -#define LOG_STR(s) PSTR(s) +// Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper) +struct LogString; -// From Arduino 2.5 onwards, we can pass a PSTR() to printf(). For previous versions, emulate support -// by copying the message to a local buffer first. String length is limited to 63 characters. +#ifdef USE_STORE_LOG_STR_IN_FLASH + +#include + +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) +#define LOG_STR_ARG(s) ((PGM_P)(s)) +#else +// Pre-Arduino 2.5, we can't pass a PSTR() to printf(). Emulate support by copying the message to a +// local buffer first. String length is limited to 63 characters. // https://github.com/esp8266/Arduino/commit/6280e98b0360f85fdac2b8f10707fffb4f6e6e31 -#include -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 5, 0) #define LOG_STR_ARG(s) \ ({ \ char __buf[64]; \ __buf[63] = '\0'; \ - strncpy_P(__buf, s, 63); \ + strncpy_P(__buf, (PGM_P)(s), 63); \ __buf; \ }) -#else -#define LOG_STR_ARG(s) (s) #endif -#else -#define LOG_STR(s) (s) -#define LOG_STR_ARG(s) (s) +#define LOG_STR(s) (reinterpret_cast(PSTR(s))) +#define LOG_STR_LITERAL(s) LOG_STR_ARG(LOG_STR(s)) + +#else // !USE_STORE_LOG_STR_IN_FLASH + +#define LOG_STR(s) (reinterpret_cast(s)) +#define LOG_STR_ARG(s) (reinterpret_cast(s)) +#define LOG_STR_LITERAL(s) (s) + #endif } // namespace esphome From 63a186bdf96e3b8db5f7291e2cd8972375a49a4c Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Mon, 13 Sep 2021 00:54:48 -0700 Subject: [PATCH 210/285] t6615: tolerate sensor dropping commands (#2255) The Amphenol T6615 has a built-in calibration system which means that the sensor could go away for a couple of seconds to figure itself out. While this is happening, commands are silently dropped. This caused the previous version of this code to lock up completely, since there was no way for the command_ state machine to tick back to the NONE state. Instead of just breaking the state machine, which might be harmful on a multi-core or multi-threaded device, add a timestamp and only break the lock if it's been more than a second since the command was issued. The command usually doesn't take more than a few milliseconds to complete, so this should not affect things unduly. While we're at it, rewrite the rx side to be more robust against bytes going missing. Instead of reading in the data essentially inline, read into a buffer and process it when enough has been read to make progress. If data stops coming when we expect it to, or the data is malformed, have a timeout that sends a new command. Co-authored-by: jas --- esphome/components/t6615/t6615.cpp | 64 ++++++++++++++++++------------ esphome/components/t6615/t6615.h | 2 + 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/esphome/components/t6615/t6615.cpp b/esphome/components/t6615/t6615.cpp index 09ff61827c..c139c56ce4 100644 --- a/esphome/components/t6615/t6615.cpp +++ b/esphome/components/t6615/t6615.cpp @@ -6,7 +6,7 @@ namespace t6615 { static const char *const TAG = "t6615"; -static const uint8_t T6615_RESPONSE_BUFFER_LENGTH = 32; +static const uint32_t T6615_TIMEOUT = 1000; static const uint8_t T6615_MAGIC = 0xFF; static const uint8_t T6615_ADDR_HOST = 0xFA; static const uint8_t T6615_ADDR_SENSOR = 0xFE; @@ -19,31 +19,49 @@ static const uint8_t T6615_COMMAND_ENABLE_ABC[] = {0xB7, 0x01}; static const uint8_t T6615_COMMAND_DISABLE_ABC[] = {0xB7, 0x02}; static const uint8_t T6615_COMMAND_SET_ELEVATION[] = {0x03, 0x0F}; -void T6615Component::loop() { - if (!this->available()) - return; +void T6615Component::send_ppm_command_() { + this->command_time_ = millis(); + this->command_ = T6615Command::GET_PPM; + this->write_byte(T6615_MAGIC); + this->write_byte(T6615_ADDR_SENSOR); + this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); + this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); +} - // Read header - uint8_t header[3]; - this->read_array(header, 3); - if (header[0] != T6615_MAGIC || header[1] != T6615_ADDR_HOST) { - ESP_LOGW(TAG, "Reading data from T6615 failed!"); - while (this->available()) - this->read(); // Clear the incoming buffer - this->status_set_warning(); +void T6615Component::loop() { + if (this->available() < 5) { + if (this->command_ == T6615Command::GET_PPM && millis() - this->command_time_ > T6615_TIMEOUT) { + /* command got eaten, clear the buffer and fire another */ + while (this->available()) + this->read(); + this->send_ppm_command_(); + } return; } - // Read body - uint8_t length = header[2]; - uint8_t response[T6615_RESPONSE_BUFFER_LENGTH]; - this->read_array(response, length); + uint8_t response_buffer[6]; + + /* by the time we get here, we know we have at least five bytes in the buffer */ + this->read_array(response_buffer, 5); + + // Read header + if (response_buffer[0] != T6615_MAGIC || response_buffer[1] != T6615_ADDR_HOST) { + ESP_LOGW(TAG, "Got bad data from T6615! Magic was %02X and address was %02X", response_buffer[0], + response_buffer[1]); + /* make sure the buffer is empty */ + while (this->available()) + this->read(); + /* try again to read the sensor */ + this->send_ppm_command_(); + this->status_set_warning(); + return; + } this->status_clear_warning(); switch (this->command_) { case T6615Command::GET_PPM: { - const uint16_t ppm = encode_uint16(response[0], response[1]); + const uint16_t ppm = encode_uint16(response_buffer[3], response_buffer[4]); ESP_LOGD(TAG, "T6615 Received CO₂=%uppm", ppm); this->co2_sensor_->publish_state(ppm); break; @@ -51,23 +69,19 @@ void T6615Component::loop() { default: break; } - + this->command_time_ = 0; this->command_ = T6615Command::NONE; } void T6615Component::update() { this->query_ppm_(); } void T6615Component::query_ppm_() { - if (this->co2_sensor_ == nullptr || this->command_ != T6615Command::NONE) { + if (this->co2_sensor_ == nullptr || + (this->command_ != T6615Command::NONE && millis() - this->command_time_ < T6615_TIMEOUT)) { return; } - this->command_ = T6615Command::GET_PPM; - - this->write_byte(T6615_MAGIC); - this->write_byte(T6615_ADDR_SENSOR); - this->write_byte(sizeof(T6615_COMMAND_GET_PPM)); - this->write_array(T6615_COMMAND_GET_PPM, sizeof(T6615_COMMAND_GET_PPM)); + this->send_ppm_command_(); } float T6615Component::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/t6615/t6615.h b/esphome/components/t6615/t6615.h index a7da3b4cf6..a075685023 100644 --- a/esphome/components/t6615/t6615.h +++ b/esphome/components/t6615/t6615.h @@ -32,8 +32,10 @@ class T6615Component : public PollingComponent, public uart::UARTDevice { protected: void query_ppm_(); + void send_ppm_command_(); T6615Command command_ = T6615Command::NONE; + unsigned long command_time_ = 0; sensor::Sensor *co2_sensor_{nullptr}; }; From 8a2b1d9359decb113e58959b7cbbfa75bc519f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 13 Sep 2021 10:06:28 +0200 Subject: [PATCH 211/285] Expose select on Frontend `web_server:` (#2245) --- esphome/components/web_server/web_server.cpp | 43 +++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index dc97bcd5c2..f82a8893eb 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -25,7 +25,8 @@ namespace web_server { static const char *const TAG = "web_server"; -void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action) { +void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &klass, const std::string &action, + const std::function &action_func = nullptr) { if (obj->is_internal()) return; stream->print("print(obj->get_name().c_str()); stream->print(""); stream->print(action.c_str()); + if (action_func) { + action_func(*stream, obj); + } stream->print(""); stream->print(""); } @@ -219,7 +223,17 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_SELECT for (auto *obj : App.get_selects()) - write_row(stream, obj, "select", ""); + write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, Nameable *obj) { + select::Select *select = (select::Select *) obj; + stream.print(""); + }); #endif stream->print(F("

    See ESPHome Web API for " @@ -648,8 +662,27 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM continue; if (obj->get_object_id() != match.id) continue; - std::string data = this->select_json(obj, obj->state); - request->send(200, "text/json", data.c_str()); + + if (request->method() == HTTP_GET) { + std::string data = this->select_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("option")) { + String option = request->getParam("option")->value(); + call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); return; } request->send(404); @@ -721,7 +754,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_SELECT - if (request->method() == HTTP_GET && match.domain == "select") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select") return true; #endif From 4e308f551cf543d721f0f394a51764fcee35b062 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Mon, 13 Sep 2021 03:08:06 -0500 Subject: [PATCH 212/285] Fix devcontainer scripts on Windows (#2239) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..94f480de94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file From c6109024aaff83c9a44ccd579f5994fafe4a3078 Mon Sep 17 00:00:00 2001 From: James Braid <251628+jamesbraid@users.noreply.github.com> Date: Mon, 13 Sep 2021 02:23:59 -0700 Subject: [PATCH 213/285] Fix SM300D2 sensor component routines so they correctly read the sensor output (#2159) --- esphome/components/sm300d2/sensor.py | 4 ++-- esphome/components/sm300d2/sm300d2.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/sm300d2/sensor.py b/esphome/components/sm300d2/sensor.py index 8452ee81f2..0c3c54f200 100644 --- a/esphome/components/sm300d2/sensor.py +++ b/esphome/components/sm300d2/sensor.py @@ -72,13 +72,13 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=0, + accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, - accuracy_decimals=0, + accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index b1787581ae..d542582fcb 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -9,10 +9,12 @@ static const uint8_t SM300D2_RESPONSE_LENGTH = 17; void SM300D2Sensor::update() { uint8_t response[SM300D2_RESPONSE_LENGTH]; + uint8_t peeked; + + while (this->available() > 0 && this->peek_byte(&peeked) && peeked != 0x3C) + this->read(); - flush(); bool read_success = read_array(response, SM300D2_RESPONSE_LENGTH); - flush(); if (!read_success) { ESP_LOGW(TAG, "Reading data from SM300D2 failed!"); @@ -63,7 +65,7 @@ void SM300D2Sensor::update() { if (this->pm_2_5_sensor_ != nullptr) this->pm_2_5_sensor_->publish_state(pm_2_5); - ESP_LOGD(TAG, "Received pm_10_0: %u µg/m³", pm_10_0); + ESP_LOGD(TAG, "Received PM10: %u µg/m³", pm_10_0); if (this->pm_10_0_sensor_ != nullptr) this->pm_10_0_sensor_->publish_state(pm_10_0); From e0cff214b215ab2cd4dfb221b37d0f56dbce141b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 11:25:28 +0200 Subject: [PATCH 214/285] Bump tzlocal from 2.1 to 3.0 (#2154) Bumps [tzlocal](https://github.com/regebro/tzlocal) from 2.1 to 3.0. - [Release notes](https://github.com/regebro/tzlocal/releases) - [Changelog](https://github.com/regebro/tzlocal/blob/master/CHANGES.txt) - [Commits](https://github.com/regebro/tzlocal/compare/2.1...3.0) --- updated-dependencies: - dependency-name: tzlocal dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index daaf86e641..7de02d8ccb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 protobuf==3.17.3 -tzlocal==2.1 +tzlocal==3.0 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 From a4867a00ea6d7170d22dda00705d010bfd0ed756 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 11:31:02 +0200 Subject: [PATCH 215/285] Activate owning-memory clang-tidy check (#1891) * Activate owning-memory clang-tidy check * Lint * Lint * Fix issue with new NfcTag constructor * Update pointers for number and select * Add back the NOLINT to display buffer * Fix merge * DSMR fixes * Nextion fixes * Fix pipsolar * Fix lwip socket * Format * Change socket fix Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .clang-tidy | 1 - esphome/components/api/api_server.cpp | 42 +++++------ esphome/components/api/api_server.h | 2 +- esphome/components/api/custom_api_device.h | 4 +- .../captive_portal/captive_portal.cpp | 2 +- .../captive_portal/captive_portal.h | 5 +- .../components/dallas/dallas_component.cpp | 16 +---- esphome/components/dallas/dallas_component.h | 6 +- esphome/components/dallas/sensor.py | 13 ++-- esphome/components/display/display_buffer.cpp | 4 +- esphome/components/dsmr/dsmr.cpp | 2 +- esphome/components/e131/e131.cpp | 2 +- .../components/fastled_base/fastled_light.cpp | 2 +- .../components/fastled_base/fastled_light.h | 2 +- esphome/components/hm3301/hm3301.cpp | 2 +- esphome/components/hm3301/hm3301.h | 2 +- .../components/http_request/http_request.cpp | 6 +- .../components/http_request/http_request.h | 7 +- esphome/components/json/json_util.cpp | 30 +++----- esphome/components/json/json_util.h | 2 + esphome/components/lcd_base/lcd_display.cpp | 2 +- esphome/components/logger/logger.cpp | 2 +- esphome/components/max7219/max7219.cpp | 2 +- esphome/components/mqtt/mqtt_component.cpp | 3 +- esphome/components/mqtt/mqtt_component.h | 4 +- .../neopixelbus/neopixelbus_light.h | 2 +- .../binary_sensor/nextion_binarysensor.cpp | 4 +- .../binary_sensor/nextion_binarysensor.h | 3 +- esphome/components/nextion/nextion.cpp | 69 ++++++++----------- esphome/components/nextion/nextion.h | 11 +-- esphome/components/nextion/nextion_base.h | 9 +-- .../nextion/nextion_component_base.h | 3 +- esphome/components/nextion/nextion_upload.cpp | 10 +-- .../nextion/sensor/nextion_sensor.cpp | 8 +-- .../nextion/sensor/nextion_sensor.h | 5 +- .../nextion/switch/nextion_switch.cpp | 4 +- .../nextion/switch/nextion_switch.h | 5 +- .../text_sensor/nextion_textsensor.cpp | 4 +- .../nextion/text_sensor/nextion_textsensor.h | 5 +- esphome/components/nfc/ndef_message.cpp | 14 ++-- esphome/components/nfc/ndef_message.h | 16 +++-- esphome/components/nfc/ndef_record.h | 6 +- esphome/components/nfc/nfc_tag.h | 21 ++++-- esphome/components/ota/ota_component.cpp | 2 +- esphome/components/ota/ota_component.h | 4 +- esphome/components/pca9685/output.py | 5 +- esphome/components/pca9685/pca9685_output.cpp | 10 +-- esphome/components/pca9685/pca9685_output.h | 9 +-- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/pn532/pn532.cpp | 20 +++--- esphome/components/pn532/pn532.h | 8 +-- .../components/pn532/pn532_mifare_classic.cpp | 10 +-- .../pn532/pn532_mifare_ultralight.cpp | 12 ++-- esphome/components/scd30/scd30.cpp | 7 +- esphome/components/sgp30/sgp30.cpp | 7 +- esphome/components/sht3xd/sht3xd.cpp | 7 +- esphome/components/shtcx/shtcx.cpp | 7 +- .../components/socket/lwip_raw_tcp_impl.cpp | 6 +- esphome/components/sps30/sps30.cpp | 7 +- esphome/components/sts3x/sts3x.cpp | 7 +- esphome/components/tlc59208f/output.py | 5 +- .../components/tlc59208f/tlc59208f_output.cpp | 10 +-- .../components/tlc59208f/tlc59208f_output.h | 9 +-- esphome/components/tm1651/tm1651.cpp | 3 +- esphome/components/tm1651/tm1651.h | 4 +- esphome/components/uart/uart_esp8266.cpp | 4 +- .../web_server_base/web_server_base.cpp | 4 +- .../web_server_base/web_server_base.h | 8 +-- esphome/components/wled/wled_light_effect.cpp | 3 +- esphome/core/esphal.cpp | 5 +- esphome/core/helpers.cpp | 15 ---- esphome/core/helpers.h | 2 +- esphome/core/preferences.cpp | 15 ++-- esphome/core/preferences.h | 23 ++++--- tests/dummy_main.cpp | 10 +-- 75 files changed, 293 insertions(+), 321 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 936100e9e6..597065d50b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,6 @@ Checks: >- -cppcoreguidelines-macro-usage, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-non-private-member-variables-in-classes, - -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c4c193b389..70f82c59db 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -64,7 +64,7 @@ void APIServer::setup() { #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { - for (auto *c : this->clients_) { + for (auto &c : this->clients_) { if (!c->remove_) c->send_log_message(level, tag, message); } @@ -77,7 +77,7 @@ void APIServer::setup() { #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr) { esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - for (auto *c : this->clients_) + for (auto &c : this->clients_) if (!c->remove_) c->send_camera_state(image); }); @@ -95,25 +95,21 @@ void APIServer::loop() { ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); auto *conn = new APIConnection(std::move(sock), this); - clients_.push_back(conn); + clients_.emplace_back(conn); conn->start(); } // Partition clients into remove and active - auto new_end = - std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); + auto new_end = std::partition(this->clients_.begin(), this->clients_.end(), + [](const std::unique_ptr &conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); } - // only then delete the pointers, otherwise log routine - // would access freed memory - for (auto it = new_end; it != this->clients_.end(); ++it) - delete *it; // resize vector this->clients_.erase(new_end, this->clients_.end()); - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { client->loop(); } @@ -169,7 +165,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_binary_sensor_state(obj, state); } #endif @@ -178,7 +174,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s void APIServer::on_cover_update(cover::Cover *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_cover_state(obj); } #endif @@ -187,7 +183,7 @@ void APIServer::on_cover_update(cover::Cover *obj) { void APIServer::on_fan_update(fan::FanState *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_fan_state(obj); } #endif @@ -196,7 +192,7 @@ void APIServer::on_fan_update(fan::FanState *obj) { void APIServer::on_light_update(light::LightState *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_light_state(obj); } #endif @@ -205,7 +201,7 @@ void APIServer::on_light_update(light::LightState *obj) { void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_sensor_state(obj, state); } #endif @@ -214,7 +210,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { void APIServer::on_switch_update(switch_::Switch *obj, bool state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_switch_state(obj, state); } #endif @@ -223,7 +219,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) { void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_text_sensor_state(obj, state); } #endif @@ -232,7 +228,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s void APIServer::on_climate_update(climate::Climate *obj) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_climate_state(obj); } #endif @@ -241,7 +237,7 @@ void APIServer::on_climate_update(climate::Climate *obj) { void APIServer::on_number_update(number::Number *obj, float state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_number_state(obj, state); } #endif @@ -250,7 +246,7 @@ void APIServer::on_number_update(number::Number *obj, float state) { void APIServer::on_select_update(select::Select *obj, const std::string &state) { if (obj->is_internal()) return; - for (auto *c : this->clients_) + for (auto &c : this->clients_) c->send_select_state(obj, state); } #endif @@ -261,7 +257,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { client->send_homeassistant_service_call(call); } } @@ -281,7 +277,7 @@ uint16_t APIServer::get_port() const { return this->port_; } void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } #ifdef USE_HOMEASSISTANT_TIME void APIServer::request_time() { - for (auto *client : this->clients_) { + for (auto &client : this->clients_) { if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) client->send_time_request(); } @@ -289,7 +285,7 @@ void APIServer::request_time() { #endif bool APIServer::is_connected() const { return !this->clients_.empty(); } void APIServer::on_shutdown() { - for (auto *c : this->clients_) { + for (auto &c : this->clients_) { c->send_disconnect_request(DisconnectRequest()); } delay(10); diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index e3fa6b18c9..d659f24358 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -91,7 +91,7 @@ class APIServer : public Component, public Controller { uint16_t port_{6053}; uint32_t reboot_timeout_{300000}; uint32_t last_connected_{0}; - std::vector clients_; + std::vector> clients_; std::string password_; std::vector state_subs_; std::vector user_services_; diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 74343904eb..9f125a6149 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -49,7 +49,7 @@ class CustomAPIDevice { template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { - auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); + auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } @@ -72,7 +72,7 @@ class CustomAPIDevice { * @param name The name of the arguments for the service, must match the arguments of the function. */ template void register_service(void (T::*callback)(), const std::string &name) { - auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); + auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); } diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 746029e011..3341769956 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -76,7 +76,7 @@ void CaptivePortal::start() { this->base_->add_ota_handler(); } - this->dns_server_ = new DNSServer(); + this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); this->dns_server_->start(53, "*", ip); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 44b1050f45..399ffaabd3 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -26,7 +27,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { this->active_ = false; this->base_->deinit(); this->dns_server_->stop(); - delete this->dns_server_; + this->dns_server_ = nullptr; } bool canHandle(AsyncWebServerRequest *request) override { @@ -65,7 +66,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; - DNSServer *dns_server_{nullptr}; + std::unique_ptr dns_server_{nullptr}; }; extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 7e34546078..847d630e7c 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -97,16 +97,7 @@ void DallasComponent::dump_config() { } } -DallasTemperatureSensor *DallasComponent::get_sensor_by_address(uint64_t address, uint8_t resolution) { - auto s = new DallasTemperatureSensor(address, resolution, this); - this->sensors_.push_back(s); - return s; -} -DallasTemperatureSensor *DallasComponent::get_sensor_by_index(uint8_t index, uint8_t resolution) { - auto s = this->get_sensor_by_address(0, resolution); - s->set_index(index); - return s; -} +void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); } void DallasComponent::update() { this->status_clear_warning(); @@ -157,11 +148,6 @@ void DallasComponent::update() { } DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} -DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent) - : parent_(parent) { - this->set_address(address); - this->set_resolution(resolution); -} void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; } diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h index d32aec1758..8d405f6eab 100644 --- a/esphome/components/dallas/dallas_component.h +++ b/esphome/components/dallas/dallas_component.h @@ -13,8 +13,7 @@ class DallasComponent : public PollingComponent { public: explicit DallasComponent(ESPOneWire *one_wire); - DallasTemperatureSensor *get_sensor_by_address(uint64_t address, uint8_t resolution); - DallasTemperatureSensor *get_sensor_by_index(uint8_t index, uint8_t resolution); + void register_sensor(DallasTemperatureSensor *sensor); void setup() override; void dump_config() override; @@ -33,8 +32,7 @@ class DallasComponent : public PollingComponent { /// Internal class that helps us create multiple sensors for one Dallas hub. class DallasTemperatureSensor : public sensor::Sensor { public: - DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent); - + void set_parent(DallasComponent *parent) { parent_ = parent; } /// Helper to get a pointer to the address as uint8_t. uint8_t *get_address8(); /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29". diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 5e09701bae..05c2c8b90c 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -36,10 +36,15 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): hub = await cg.get_variable(config[CONF_DALLAS_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + if CONF_ADDRESS in config: - address = config[CONF_ADDRESS] - rhs = hub.Pget_sensor_by_address(address, config.get(CONF_RESOLUTION)) + cg.add(var.set_address(config[CONF_ADDRESS])) else: - rhs = hub.Pget_sensor_by_index(config[CONF_INDEX], config.get(CONF_RESOLUTION)) - var = cg.Pvariable(config[CONF_ID], rhs) + cg.add(var.set_index(config[CONF_INDEX])) + + if CONF_RESOLUTION in config: + cg.add(var.set_resolution(config[CONF_RESOLUTION])) + + cg.add(hub.register_sensor(var)) await sensor.register_sensor(var, config) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index c330d00ce5..9c4ee3189f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -1,9 +1,9 @@ #include "display_buffer.h" +#include #include "esphome/core/application.h" #include "esphome/core/color.h" #include "esphome/core/log.h" -#include namespace esphome { namespace display { @@ -14,7 +14,7 @@ const Color COLOR_OFF(0, 0, 0, 0); const Color COLOR_ON(255, 255, 255, 255); void DisplayBuffer::init_internal_(uint32_t buffer_length) { - this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; + this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT if (this->buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate buffer for display!"); return; diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 9bce7a382f..39f05a28d1 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -105,7 +105,7 @@ void Dsmr::receive_encrypted_() { &buffer[18], // cipher size buffer_length - 17); - delete gcmaes128; + delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index bd749683c5..7694d039e5 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -26,7 +26,7 @@ E131Component::~E131Component() { } void E131Component::setup() { - udp_.reset(new WiFiUDP()); + udp_ = make_unique(); if (!udp_->begin(PORT)) { ESP_LOGE(TAG, "Cannot bind E131 to %d.", PORT); diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index edfeb401f1..15a5c8984c 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -10,7 +10,7 @@ void FastLEDLightOutput::setup() { ESP_LOGCONFIG(TAG, "Setting up FastLED light..."); this->controller_->init(); this->controller_->setLeds(this->leds_, this->num_leds_); - this->effect_data_ = new uint8_t[this->num_leds_]; + this->effect_data_ = new uint8_t[this->num_leds_]; // NOLINT if (!this->max_refresh_rate_.has_value()) { this->set_max_refresh_rate(this->controller_->getMaxRefreshRate()); } diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index ee85735dea..d1c470599d 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -30,7 +30,7 @@ class FastLEDLightOutput : public light::AddressableLight { CLEDController &add_leds(CLEDController *controller, int num_leds) { this->controller_ = controller; this->num_leds_ = num_leds; - this->leds_ = new CRGB[num_leds]; + this->leds_ = new CRGB[num_leds]; // NOLINT for (int i = 0; i < this->num_leds_; i++) this->leds_[i] = CRGB::Black; diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 5612867d1b..96c1ec0ee9 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -12,7 +12,7 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7; void HM3301Component::setup() { ESP_LOGCONFIG(TAG, "Setting up HM3301..."); - hm3301_ = new HM330X(); + hm3301_ = make_unique(); error_code_ = hm3301_->init(); if (error_code_ != NO_ERROR) { this->mark_failed(); diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 5594f1719c..b024f93719 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -27,7 +27,7 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void update() override; protected: - HM330X *hm3301_; + std::unique_ptr hm3301_; HM330XErrorCode error_code_{NO_ERROR}; diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0fafa8cd86..bf5b24c4f2 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -75,10 +75,10 @@ void HttpRequestComponent::send(const std::vector } #ifdef ARDUINO_ARCH_ESP8266 -WiFiClient *HttpRequestComponent::get_wifi_client_() { +std::shared_ptr HttpRequestComponent::get_wifi_client_() { if (this->secure_) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_ = std::make_shared(); this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -86,7 +86,7 @@ WiFiClient *HttpRequestComponent::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); + this->wifi_client_ = std::make_shared(); } return this->wifi_client_; } diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 27919fe75d..740a4580b4 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef ARDUINO_ARCH_ESP32 #include @@ -51,9 +52,9 @@ class HttpRequestComponent : public Component { std::string body_; std::list

    headers_; #ifdef ARDUINO_ARCH_ESP8266 - WiFiClient *wifi_client_{nullptr}; - BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; - WiFiClient *get_wifi_client_(); + std::shared_ptr wifi_client_; + std::shared_ptr wifi_client_secure_; + std::shared_ptr get_wifi_client_(); #endif }; diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 4720c8f9c9..aab5b1ce9b 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -6,21 +6,7 @@ namespace json { static const char *const TAG = "json"; -static char *global_json_build_buffer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static size_t global_json_build_buffer_size = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void reserve_global_json_build_buffer(size_t required_size) { - if (global_json_build_buffer_size == 0 || global_json_build_buffer_size < required_size) { - delete[] global_json_build_buffer; - global_json_build_buffer_size = std::max(required_size, global_json_build_buffer_size * 2); - - size_t remainder = global_json_build_buffer_size % 16U; - if (remainder != 0) - global_json_build_buffer_size += 16 - remainder; - - global_json_build_buffer = new char[global_json_build_buffer_size]; - } -} +static std::vector global_json_build_buffer; // NOLINT const char *build_json(const json_build_t &f, size_t *length) { global_json_buffer.clear(); @@ -35,16 +21,16 @@ const char *build_json(const json_build_t &f, size_t *length) { // Discovery | 372 | 356 | // Discovery | 336 | 311 | // Discovery | 408 | 393 | - reserve_global_json_build_buffer(global_json_buffer.size()); - size_t bytes_written = root.printTo(global_json_build_buffer, global_json_build_buffer_size); + global_json_build_buffer.reserve(global_json_buffer.size() + 1); + size_t bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity()); - if (bytes_written >= global_json_build_buffer_size - 1) { - reserve_global_json_build_buffer(root.measureLength() + 1); - bytes_written = root.printTo(global_json_build_buffer, global_json_build_buffer_size); + if (bytes_written >= global_json_build_buffer.capacity() - 1) { + global_json_build_buffer.reserve(root.measureLength() + 1); + bytes_written = root.printTo(global_json_build_buffer.data(), global_json_build_buffer.capacity()); } *length = bytes_written; - return global_json_build_buffer; + return global_json_build_buffer.data(); } void parse_json(const std::string &data, const json_parse_t &f) { global_json_buffer.clear(); @@ -113,7 +99,7 @@ void VectorJsonBuffer::reserve(size_t size) { // NOLINT target_capacity *= 2; char *old_buffer = this->buffer_; - this->buffer_ = new char[target_capacity]; + this->buffer_ = new char[target_capacity]; // NOLINT if (old_buffer != nullptr && this->capacity_ != 0) { this->free_blocks_.push_back(old_buffer); memcpy(this->buffer_, old_buffer, this->capacity_); diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index aafd039b9a..65b2496b85 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/helpers.h" #include diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 95b349b9c1..4c0d0ab3ea 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -29,7 +29,7 @@ static const uint8_t LCD_DISPLAY_FUNCTION_2_LINE = 0x08; static const uint8_t LCD_DISPLAY_FUNCTION_5X10_DOTS = 0x04; void LCDDisplay::setup() { - this->buffer_ = new uint8_t[this->rows_ * this->columns_]; + this->buffer_ = new uint8_t[this->rows_ * this->columns_]; // NOLINT for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) this->buffer_[i] = ' '; diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 9d79037087..59dfa93429 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -123,7 +123,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) { // add 1 to buffer size for null terminator - this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; + this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT } void Logger::pre_setup() { diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 91bea22e46..97886e53fa 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -117,7 +117,7 @@ float MAX7219Component::get_setup_priority() const { return setup_priority::PROC void MAX7219Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MAX7219..."); this->spi_setup(); - this->buffer_ = new uint8_t[this->num_chips_ * 8]; + this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT for (uint8_t i = 0; i < this->num_chips_ * 8; i++) this->buffer_[i] = 0; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 6ddc080b53..6e376d0fef 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -139,8 +139,7 @@ void MQTTComponent::set_custom_command_topic(const std::string &custom_command_t void MQTTComponent::set_availability(std::string topic, std::string payload_available, std::string payload_not_available) { - delete this->availability_; - this->availability_ = new Availability(); + this->availability_ = make_unique(); this->availability_->topic = std::move(topic); this->availability_->payload_available = std::move(payload_available); this->availability_->payload_not_available = std::move(payload_not_available); diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 4d7a522d5f..83a0c06644 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "mqtt_client.h" @@ -171,7 +173,7 @@ class MQTTComponent : public Component { std::string custom_command_topic_{}; bool retain_{true}; bool discovery_enabled_{true}; - Availability *availability_{nullptr}; + std::unique_ptr availability_; bool resend_state_{false}; }; diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 6fa3fb3cd9..5359bac61b 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -79,7 +79,7 @@ class NeoPixelBusLightOutputBase : public light::AddressableLight { (*this)[i] = Color(0, 0, 0, 0); } - this->effect_data_ = new uint8_t[this->size()]; + this->effect_data_ = new uint8_t[this->size()]; // NOLINT this->controller_->Begin(); } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index bf6e74cb38..c5bfa78efe 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -33,7 +33,7 @@ void NextionBinarySensor::update() { if (this->variable_name_.empty()) // This is a touch component return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { @@ -48,7 +48,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h index b6b23ada85..b86ee74013 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -10,7 +10,8 @@ class NextionBinarySensor; class NextionBinarySensor : public NextionComponent, public binary_sensor::BinarySensorInitiallyOff, - public PollingComponent { + public PollingComponent, + public std::enable_shared_from_this { public: NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 3f44fe4075..d56c370412 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -196,7 +196,7 @@ void Nextion::print_queue_members_() { ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); ESP_LOGN(TAG, "*******************************************"); int count = 0; - for (auto *i : this->nextion_queue_) { + for (auto &i : this->nextion_queue_) { if (count++ == 10) break; @@ -257,8 +257,9 @@ bool Nextion::remove_from_q_(bool report_empty) { return false; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -266,10 +267,8 @@ bool Nextion::remove_from_q_(bool report_empty) { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } - delete component; } - delete nb; - this->nextion_queue_.pop_front(); + return true; } @@ -358,7 +357,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - NextionComponentBase *component = nb->component; + auto &component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", @@ -369,9 +368,6 @@ void Nextion::process_nextion_commands_() { found = index; - delete component; - delete nb; - break; } ++index; @@ -468,8 +464,9 @@ void Nextion::process_nextion_commands_() { break; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", @@ -480,9 +477,6 @@ void Nextion::process_nextion_commands_() { component->set_state_from_string(to_process, true, false); } - delete nb; - this->nextion_queue_.pop_front(); - break; } // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF @@ -511,8 +505,9 @@ void Nextion::process_nextion_commands_() { ++dataindex; } - NextionQueue *nb = this->nextion_queue_.front(); - NextionComponentBase *component = nb->component; + auto nb = std::move(this->nextion_queue_.front()); + this->nextion_queue_.pop_front(); + auto &component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && component->get_queue_type() != NextionQueueType::BINARY_SENSOR && @@ -526,9 +521,6 @@ void Nextion::process_nextion_commands_() { component->set_state_from_int(value, true, false); } - delete nb; - this->nextion_queue_.pop_front(); - break; } @@ -690,7 +682,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto component = nb->component; + auto &component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() : 255; // ADDT command can only send 255 @@ -707,8 +699,6 @@ void Nextion::process_nextion_commands_() { component->get_wave_buffer().begin() + buffer_to_send); } found = index; - delete component; - delete nb; break; } ++index; @@ -737,7 +727,7 @@ void Nextion::process_nextion_commands_() { if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { for (int i = 0; i < this->nextion_queue_.size(); i++) { - NextionComponentBase *component = this->nextion_queue_[i]->component; + auto &component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", @@ -754,12 +744,10 @@ void Nextion::process_nextion_commands_() { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } - delete component; } - delete this->nextion_queue_[i]; - this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + i--; } else { break; @@ -911,16 +899,16 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool * @param variable_name Name for the queue */ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); - nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component = make_unique(); nextion_queue->component->set_variable_name(variable_name); nextion_queue->queue_time = millis(); - this->nextion_queue_.push_back(nextion_queue); - ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); + + this->nextion_queue_.push_back(std::move(nextion_queue)); } /** @@ -991,7 +979,7 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { +void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1019,7 +1007,8 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia * @param state_value String value to set * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { +void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1039,11 +1028,11 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } -void Nextion::add_to_get_queue(NextionComponentBase *component) { +void Nextion::add_to_get_queue(std::shared_ptr component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); nextion_queue->component = component; nextion_queue->queue_time = millis(); @@ -1054,7 +1043,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { std::string command = "get " + component->get_variable_name_to_send(); if (this->send_command_(command)) { - this->nextion_queue_.push_back(nextion_queue); + this->nextion_queue_.push_back(std::move(nextion_queue)); } } @@ -1066,13 +1055,13 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { * @param buffer_to_send The buffer size * @param buffer_size The buffer data */ -void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { +void Nextion::add_addt_command_to_queue(std::shared_ptr component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + auto nextion_queue = make_unique(); - nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component = std::make_shared(); nextion_queue->queue_time = millis(); size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() @@ -1081,7 +1070,7 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { std::string command = "addt " + to_string(component->get_component_id()) + "," + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); if (this->send_command_(command)) { - this->nextion_queue_.push_back(nextion_queue); + this->nextion_queue_.push_back(std::move(nextion_queue)); } } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 3a43a51975..45c4a629c4 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -707,17 +707,18 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); void set_nextion_text_state(const std::string &name, const std::string &state); - void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) override; - void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) override; - void add_to_get_queue(NextionComponentBase *component) override; + void add_to_get_queue(std::shared_ptr component) override; - void add_addt_command_to_queue(NextionComponentBase *component) override; + void add_addt_command_to_queue(std::shared_ptr component) override; void update_components_by_prefix(const std::string &prefix); @@ -728,7 +729,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - std::deque nextion_queue_; + std::deque> nextion_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index a24fd74060..d91c70c960 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,18 +24,19 @@ class NextionBase; class NextionBase { public: - virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) = 0; - virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(std::shared_ptr component, + const std::string &state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) = 0; - virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + virtual void add_addt_command_to_queue(std::shared_ptr component) = 0; - virtual void add_to_get_queue(NextionComponentBase *component) = 0; + virtual void add_to_get_queue(std::shared_ptr component) = 0; virtual void set_component_background_color(const char *component, Color color) = 0; virtual void set_component_pressed_background_color(const char *component, Color color) = 0; diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index 71ad803bc4..2725d5a30c 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "esphome/core/defines.h" namespace esphome { @@ -22,7 +23,7 @@ class NextionComponentBase; class NextionQueue { public: virtual ~NextionQueue() = default; - NextionComponentBase *component; + std::shared_ptr component; uint32_t queue_time = 0; }; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 43c8867e56..a83618e888 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -275,12 +275,12 @@ void Nextion::upload_tft() { } else { #endif ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; - if (this->transfer_buffer_ == nullptr) { // Try a smaller size + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) + if (this->transfer_buffer_ == nullptr) { // Try a smaller size ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); chunk_size = 4096; ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = new uint8_t[chunk_size]; + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) if (!this->transfer_buffer_) this->upload_end_(); @@ -322,7 +322,7 @@ void Nextion::upload_end_() { WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); // NOLINT(cppcoreguidelines-owning-memory) this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -330,7 +330,7 @@ WiFiClient *Nextion::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); + this->wifi_client_ = new WiFiClient(); // NOLINT(cppcoreguidelines-owning-memory) } return this->wifi_client_; } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index bbcb465d85..1e79164546 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -34,7 +34,7 @@ void NextionSensor::update() { return; if (this->wave_chan_id_ == UINT8_MAX) { - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } else { if (this->send_last_value_) { this->add_to_wave_buffer(this->last_value_); @@ -62,9 +62,9 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { double to_multiply = pow(10, this->precision_); int state_value = (int) (state * to_multiply); - this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state_value); } else { - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } } @@ -103,7 +103,7 @@ void NextionSensor::wave_update_() { buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); #endif - this->nextion_->add_addt_command_to_queue(this); + this->nextion_->add_addt_command_to_queue(shared_from_this()); } } // namespace nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index e4dde9a513..068ff0451b 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionSensor; -class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { +class NextionSensor : public NextionComponent, + public sensor::Sensor, + public PollingComponent, + public std::enable_shared_from_this { public: NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } void send_state_to_nextion() override { this->set_state(this->state, false, true); }; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 1f32ad3425..0bd958e0d8 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -20,7 +20,7 @@ void NextionSwitch::process_bool(const std::string &variable_name, bool on) { void NextionSwitch::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { @@ -32,7 +32,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); } } if (publish) { diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h index 1548287473..d7783e5c51 100644 --- a/esphome/components/nextion/switch/nextion_switch.h +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionSwitch; -class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { +class NextionSwitch : public NextionComponent, + public switch_::Switch, + public PollingComponent, + public std::enable_shared_from_this { public: NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index 08f032df74..fa7cb35025 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -18,7 +18,7 @@ void NextionTextSensor::process_text(const std::string &variable_name, const std void NextionTextSensor::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(this); + this->nextion_->add_to_get_queue(shared_from_this()); } void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { @@ -29,7 +29,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s if (this->nextion_->is_sleeping() || !this->visible_) { this->needs_to_send_update_ = true; } else { - this->nextion_->add_no_result_to_queue_with_set(this, state); + this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), state); } } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h index 5716d0a008..762797727d 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.h +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -8,7 +8,10 @@ namespace esphome { namespace nextion { class NextionTextSensor; -class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { +class NextionTextSensor : public NextionComponent, + public text_sensor::TextSensor, + public PollingComponent, + public std::enable_shared_from_this { public: NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } void update() override; diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index 4e295d4469..b1554f41ae 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -17,7 +17,7 @@ NdefMessage::NdefMessage(std::vector &data) { ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); - auto record = new NdefRecord(); + auto record = make_unique(); record->set_tnf(tnf); uint8_t type_length = data[index++]; @@ -62,20 +62,20 @@ NdefMessage::NdefMessage(std::vector &data) { record->set_payload(payload_str); index += payload_length; - this->add_record(record); ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); + this->add_record(std::move(record)); if (me) break; } } -bool NdefMessage::add_record(NdefRecord *record) { +bool NdefMessage::add_record(std::unique_ptr record) { if (this->records_.size() >= MAX_NDEF_RECORDS) { ESP_LOGE(TAG, "Too many records. Max: %d", MAX_NDEF_RECORDS); return false; } - this->records_.push_back(record); + this->records_.emplace_back(std::move(record)); return true; } @@ -83,13 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - auto r = new NdefRecord(TNF_WELL_KNOWN, "T", payload); - return this->add_record(r); + return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); } bool NdefMessage::add_uri_record(const std::string &uri) { - auto r = new NdefRecord(TNF_WELL_KNOWN, "U", uri); - return this->add_record(r); + return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index e47a7b992a..d2ffa9582e 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "ndef_record.h" @@ -11,12 +13,18 @@ static const uint8_t MAX_NDEF_RECORDS = 4; class NdefMessage { public: - NdefMessage(){}; + NdefMessage() = default; NdefMessage(std::vector &data); + NdefMessage(const NdefMessage &msg) { + records_.reserve(msg.records_.size()); + for (const auto &r : msg.records_) { + records_.emplace_back(make_unique(*r)); + } + } - std::vector get_records() { return this->records_; }; + const std::vector> &get_records() { return this->records_; }; - bool add_record(NdefRecord *record); + bool add_record(std::unique_ptr record); bool add_text_record(const std::string &text); bool add_text_record(const std::string &text, const std::string &encoding); bool add_uri_record(const std::string &uri); @@ -24,7 +32,7 @@ class NdefMessage { std::vector encode(); protected: - std::vector records_; + std::vector> records_; }; } // namespace nfc diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h index 2059444882..680fe20986 100644 --- a/esphome/components/nfc/ndef_record.h +++ b/esphome/components/nfc/ndef_record.h @@ -85,9 +85,9 @@ class NdefRecord { std::vector encode(bool first, bool last); uint8_t get_tnf_byte(bool first, bool last); - const std::string &get_type() { return this->type_; }; - const std::string &get_id() { return this->id_; }; - const std::string &get_payload() { return this->payload_; }; + const std::string &get_type() const { return this->type_; }; + const std::string &get_id() const { return this->id_; }; + const std::string &get_payload() const { return this->payload_; }; protected: uint8_t tnf_; diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ff0d1c39b4..ab6ca650e4 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -1,6 +1,9 @@ #pragma once +#include + #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #include "ndef_message.h" namespace esphome { @@ -20,27 +23,33 @@ class NfcTag { this->uid_ = uid; this->tag_type_ = tag_type; }; - NfcTag(std::vector &uid, const std::string &tag_type, nfc::NdefMessage *ndef_message) { + NfcTag(std::vector &uid, const std::string &tag_type, std::unique_ptr ndef_message) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = ndef_message; + this->ndef_message_ = std::move(ndef_message); }; NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = new NdefMessage(ndef_data); + this->ndef_message_ = make_unique(ndef_data); }; + NfcTag(const NfcTag &rhs) { + uid_ = rhs.uid_; + tag_type_ = rhs.tag_type_; + if (rhs.ndef_message_ != nullptr) + ndef_message_ = make_unique(*rhs.ndef_message_); + } std::vector &get_uid() { return this->uid_; }; const std::string &get_tag_type() { return this->tag_type_; }; bool has_ndef_message() { return this->ndef_message_ != nullptr; }; - NdefMessage *get_ndef_message() { return this->ndef_message_; }; - void set_ndef_message(NdefMessage *ndef_message) { this->ndef_message_ = ndef_message; }; + const std::unique_ptr &get_ndef_message() { return this->ndef_message_; }; + void set_ndef_message(std::unique_ptr ndef_message) { this->ndef_message_ = std::move(ndef_message); }; protected: std::vector uid_; std::string tag_type_; - NdefMessage *ndef_message_{nullptr}; + std::unique_ptr ndef_message_; }; } // namespace nfc diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index ac72befb9e..e8ac1b9bcd 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -19,7 +19,7 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; void OTAComponent::setup() { - this->server_ = new WiFiServer(this->port_); + this->server_ = make_unique(this->port_); this->server_->begin(); this->dump_config(); diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 8b5830295e..04cea56ec2 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" @@ -80,7 +82,7 @@ class OTAComponent : public Component { uint16_t port_; - WiFiServer *server_{nullptr}; + std::unique_ptr server_{nullptr}; WiFiClient client_{}; bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled. diff --git a/esphome/components/pca9685/output.py b/esphome/components/pca9685/output.py index 40f7b3cd74..b7681f9ba0 100644 --- a/esphome/components/pca9685/output.py +++ b/esphome/components/pca9685/output.py @@ -20,6 +20,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( async def to_code(config): paren = await cg.get_variable(config[CONF_PCA9685_ID]) - rhs = paren.create_channel(config[CONF_CHANNEL]) - var = cg.Pvariable(config[CONF_ID], rhs) + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(paren.register_channel(var)) await output.register_output(var, config) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index c3fd2b60e4..1ad6f4a665 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -123,11 +123,11 @@ void PCA9685Output::loop() { this->update_ = false; } -PCA9685Channel *PCA9685Output::create_channel(uint8_t channel) { - this->min_channel_ = std::min(this->min_channel_, channel); - this->max_channel_ = std::max(this->max_channel_, channel); - auto *c = new PCA9685Channel(this, channel); - return c; +void PCA9685Output::register_channel(PCA9685Channel *channel) { + auto c = channel->channel_; + this->min_channel_ = std::min(this->min_channel_, c); + this->max_channel_ = std::max(this->max_channel_, c); + channel->set_parent(this); } void PCA9685Channel::write_state(float state) { diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index 55b52fc660..bd3f7f15f9 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -20,14 +20,15 @@ extern const uint8_t PCA9685_MODE_OUTNE_LOW; class PCA9685Output; -class PCA9685Channel : public output::FloatOutput { +class PCA9685Channel : public output::FloatOutput, public Parented { public: - PCA9685Channel(PCA9685Output *parent, uint8_t channel) : parent_(parent), channel_(channel) {} + void set_channel(uint8_t channel) { channel_ = channel; } protected: + friend class PCA9685Output; + void write_state(float state) override; - PCA9685Output *parent_; uint8_t channel_; }; @@ -37,7 +38,7 @@ class PCA9685Output : public Component, public i2c::I2CDevice { PCA9685Output(float frequency, uint8_t mode = PCA9685_MODE_OUTPUT_ONACK | PCA9685_MODE_OUTPUT_TOTEM_POLE) : frequency_(frequency), mode_(mode) {} - PCA9685Channel *create_channel(uint8_t channel); + void register_channel(PCA9685Channel *channel); void setup() override; void dump_config() override; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 9c7adc13c1..8603adfbf4 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -881,7 +881,7 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll size_t length = strlen(command) + 1; const char *beg = command; const char *end = command + length; - used_polling_command.command = new uint8_t[length]; + used_polling_command.command = new uint8_t[length]; // NOLINT(cppcoreguidelines-owning-memory) size_t i = 0; for (; beg != end; ++beg, ++i) { used_polling_command.command[i] = (uint8_t)(*beg); diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index fcab9872c4..8fda19cba3 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -104,7 +104,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = new nfc::NfcTag(this->current_uid_); + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +117,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = new nfc::NfcTag(this->current_uid_); + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -158,10 +158,10 @@ void PN532::loop() { if (report) { ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str()); if (tag->has_ndef_message()) { - auto message = tag->get_ndef_message(); - auto records = message->get_records(); + const auto &message = tag->get_ndef_message(); + const auto &records = message->get_records(); ESP_LOGD(TAG, " NDEF formatted records:"); - for (auto &record : records) { + for (const auto &record : records) { ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); } } @@ -270,7 +270,7 @@ void PN532::turn_off_rf_() { }); } -nfc::NfcTag *PN532::read_tag_(std::vector &uid) { +std::unique_ptr PN532::read_tag_(std::vector &uid) { uint8_t type = nfc::guess_tag_type(uid.size()); if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { @@ -281,9 +281,9 @@ nfc::NfcTag *PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return new nfc::NfcTag(uid); + return make_unique(uid); } else { - return new nfc::NfcTag(uid); + return make_unique(uid); } } @@ -373,7 +373,9 @@ bool PN532BinarySensor::process(std::vector &data) { this->found_ = true; return true; } -void PN532OnTagTrigger::process(nfc::NfcTag *tag) { this->trigger(nfc::format_uid(tag->get_uid()), *tag); } +void PN532OnTagTrigger::process(const std::unique_ptr &tag) { + this->trigger(nfc::format_uid(tag->get_uid()), *tag); +} } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index c4854cf7f2..692a5011e6 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -54,13 +54,13 @@ class PN532 : public PollingComponent { virtual bool read_data(std::vector &data, uint8_t len) = 0; virtual bool read_response(uint8_t command, std::vector &data) = 0; - nfc::NfcTag *read_tag_(std::vector &uid); + std::unique_ptr read_tag_(std::vector &uid); bool format_tag_(std::vector &uid); bool clean_tag_(std::vector &uid); bool write_tag_(std::vector &uid, nfc::NdefMessage *message); - nfc::NfcTag *read_mifare_classic_tag_(std::vector &uid); + std::unique_ptr read_mifare_classic_tag_(std::vector &uid); bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); bool auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); @@ -68,7 +68,7 @@ class PN532 : public PollingComponent { bool format_mifare_classic_ndef_(std::vector &uid); bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); - nfc::NfcTag *read_mifare_ultralight_tag_(std::vector &uid); + std::unique_ptr read_mifare_ultralight_tag_(std::vector &uid); bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); bool is_mifare_ultralight_formatted_(); uint16_t read_mifare_ultralight_capacity_(); @@ -117,7 +117,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { class PN532OnTagTrigger : public Trigger { public: - void process(nfc::NfcTag *tag); + void process(const std::unique_ptr &tag); }; class PN532OnFinishedWriteTrigger : public Trigger<> { diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index 119d1ceef6..fa99e5cfab 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -6,7 +6,7 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_classic"; -nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { +std::unique_ptr PN532::read_mifare_classic_tag_(std::vector &uid) { uint8_t current_block = 4; uint8_t message_start_index = 0; uint32_t message_length = 0; @@ -15,15 +15,15 @@ nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return new nfc::NfcTag(uid, nfc::ERROR); + return make_unique(uid, nfc::ERROR); } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + return make_unique(uid, nfc::MIFARE_CLASSIC); } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + return make_unique(uid, nfc::MIFARE_CLASSIC); } uint32_t index = 0; @@ -51,7 +51,7 @@ nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { } } buffer.erase(buffer.begin(), buffer.begin() + message_start_index); - return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer); + return make_unique(uid, nfc::MIFARE_CLASSIC, buffer); } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 1787a3c225..6f118419ba 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -6,28 +6,28 @@ namespace pn532 { static const char *const TAG = "pn532.mifare_ultralight"; -nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { +std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +38,7 @@ nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { data.erase(data.begin(), data.begin() + message_start_index); data.erase(data.begin() + message_length, data.end()); - return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data); + return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 838c92f04e..8dfd992abe 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -221,10 +221,9 @@ uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -233,13 +232,11 @@ bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index e30d6d3adc..5a6f438429 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -332,10 +332,9 @@ uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) { bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -344,13 +343,11 @@ bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 0bf2cc1a81..be5b93c124 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -101,10 +101,9 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -113,13 +112,11 @@ bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 848da8d7d7..db3a3bf803 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -130,10 +130,9 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -142,13 +141,11 @@ bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index aaeee7268a..29e1b23823 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -423,9 +423,9 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto *sock = new LWIPRawImpl(newpcb); + auto sock = std::unique_ptr(new LWIPRawImpl(newpcb)); sock->init(); - accepted_sockets_.emplace(sock); + accepted_sockets_.push(std::move(sock)); return ERR_OK; } void err_fn(err_t err) { @@ -486,7 +486,7 @@ std::unique_ptr socket(int domain, int type, int protocol) { auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; - auto *sock = new LWIPRawImpl(pcb); + auto *sock = new LWIPRawImpl(pcb); // NOLINT(cppcoreguidelines-owning-memory) sock->init(); return std::unique_ptr{sock}; } diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 30bd134626..3a31f4d607 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -242,10 +242,9 @@ bool SPS30Component::start_continuous_measurement_() { bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -254,13 +253,11 @@ bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sht_crc_(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index 5d6d725a17..77383c6daa 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -97,10 +97,9 @@ uint8_t sts3x_crc(uint8_t data1, uint8_t data2) { bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; - auto *buf = new uint8_t[num_bytes]; + std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf, num_bytes)) { - delete[](buf); + if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { return false; } @@ -109,13 +108,11 @@ bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { uint8_t crc = sts3x_crc(buf[j], buf[j + 1]); if (crc != buf[j + 2]) { ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc); - delete[](buf); return false; } data[i] = (buf[j] << 8) | buf[j + 1]; } - delete[](buf); return true; } diff --git a/esphome/components/tlc59208f/output.py b/esphome/components/tlc59208f/output.py index f7cc89b252..ac45909673 100644 --- a/esphome/components/tlc59208f/output.py +++ b/esphome/components/tlc59208f/output.py @@ -20,6 +20,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( async def to_code(config): paren = await cg.get_variable(config[CONF_TLC59208F_ID]) - rhs = paren.create_channel(config[CONF_CHANNEL]) - var = cg.Pvariable(config[CONF_ID], rhs) + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(paren.register_channel(var)) await output.register_output(var, config) diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index 784a0947ed..e416a5c62b 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -137,11 +137,11 @@ void TLC59208FOutput::loop() { this->update_ = false; } -TLC59208FChannel *TLC59208FOutput::create_channel(uint8_t channel) { - this->min_channel_ = std::min(this->min_channel_, channel); - this->max_channel_ = std::max(this->max_channel_, channel); - auto *c = new TLC59208FChannel(this, channel); - return c; +void TLC59208FOutput::register_channel(TLC59208FChannel *channel) { + auto c = channel->channel_; + this->min_channel_ = std::min(this->min_channel_, c); + this->max_channel_ = std::max(this->max_channel_, c); + channel->set_parent(this); } void TLC59208FChannel::write_state(float state) { diff --git a/esphome/components/tlc59208f/tlc59208f_output.h b/esphome/components/tlc59208f/tlc59208f_output.h index 06b7adc882..79fccc97f3 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.h +++ b/esphome/components/tlc59208f/tlc59208f_output.h @@ -21,14 +21,15 @@ extern const uint8_t TLC59208F_MODE2_WDT_35MS; class TLC59208FOutput; -class TLC59208FChannel : public output::FloatOutput { +class TLC59208FChannel : public output::FloatOutput, public Parented { public: - TLC59208FChannel(TLC59208FOutput *parent, uint8_t channel) : parent_(parent), channel_(channel) {} + void set_channel(uint8_t channel) { channel_ = channel; } protected: + friend class TLC59208FOutput; + void write_state(float state) override; - TLC59208FOutput *parent_; uint8_t channel_; }; @@ -37,7 +38,7 @@ class TLC59208FOutput : public Component, public i2c::I2CDevice { public: TLC59208FOutput(uint8_t mode = TLC59208F_MODE2_OCH) : mode_(mode) {} - TLC59208FChannel *create_channel(uint8_t channel); + void register_channel(TLC59208FChannel *channel); void setup() override; void dump_config() override; diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index bb37e9f4c9..3689ef5894 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -1,5 +1,6 @@ #include "tm1651.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" namespace esphome { namespace tm1651 { @@ -19,7 +20,7 @@ void TM1651Display::setup() { uint8_t clk = clk_pin_->get_pin(); uint8_t dio = dio_pin_->get_pin(); - battery_display_ = new TM1651(clk, dio); + battery_display_ = make_unique(clk, dio); battery_display_->init(); battery_display_->clearDisplay(); } diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index 6eab24687c..a18519bf41 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include "esphome/core/automation.h" @@ -25,7 +27,7 @@ class TM1651Display : public Component { void turn_off(); protected: - TM1651 *battery_display_; + std::unique_ptr battery_display_; GPIOPin *clk_pin_; GPIOPin *dio_pin_; bool is_on_ = true; diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index bc6b0f72d3..6d207fde6c 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -85,7 +85,7 @@ void UARTComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } else { - this->sw_serial_ = new ESP8266SoftwareSerial(); + this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, @@ -227,7 +227,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra pin.setup(); this->gpio_rx_pin_ = &pin; this->rx_pin_ = pin.to_isr(); - this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; + this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); } } diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 85711704b9..9f04c3b7bf 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -86,7 +86,9 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { request->send(response); } -void WebServerBase::add_ota_handler() { this->add_handler(new OTARequestHandler(this)); } +void WebServerBase::add_ota_handler() { + this->add_handler(new OTARequestHandler(this)); // NOLINT +} float WebServerBase::get_setup_priority() const { // Before WiFi (captive portal) return setup_priority::WIFI + 2.0f; diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index b6024ceafa..158006ae40 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,6 @@ #pragma once +#include #include "esphome/core/component.h" #include @@ -14,7 +15,7 @@ class WebServerBase : public Component { this->initialized_++; return; } - this->server_ = new AsyncWebServer(this->port_); + this->server_ = std::make_shared(this->port_); this->server_->begin(); for (auto *handler : this->handlers_) @@ -25,11 +26,10 @@ class WebServerBase : public Component { void deinit() { this->initialized_--; if (this->initialized_ == 0) { - delete this->server_; this->server_ = nullptr; } } - AsyncWebServer *get_server() const { return server_; } + std::shared_ptr get_server() const { return server_; } float get_setup_priority() const override; void add_handler(AsyncWebHandler *handler) { @@ -50,7 +50,7 @@ class WebServerBase : public Component { int initialized_{0}; uint16_t port_{80}; - AsyncWebServer *server_{nullptr}; + std::shared_ptr server_{nullptr}; std::vector handlers_; }; diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 915d1c6cc2..500b9bbad2 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -1,5 +1,6 @@ #include "wled_light_effect.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef ARDUINO_ARCH_ESP32 #include @@ -48,7 +49,7 @@ void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) { void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { // Init UDP lazily if (!udp_) { - udp_.reset(new WiFiUDP()); + udp_ = make_unique(); if (!udp_->begin(port_)) { ESP_LOGW(TAG, "Cannot bind WLEDLightEffect to %d.", port_); diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp index 8fb1dd86c5..849fdf95ad 100644 --- a/esphome/core/esphal.cpp +++ b/esphome/core/esphal.cpp @@ -230,10 +230,11 @@ void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const } } #ifdef ARDUINO_ARCH_ESP8266 - ArgStructure *as = new ArgStructure; + ArgStructure *as = new ArgStructure; // NOLINT as->interruptInfo = nullptr; as->functionInfo = new ESPHomeInterruptFuncInfo{ + // NOLINT .func = func, .arg = arg, }; @@ -249,7 +250,7 @@ void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const } ISRInternalGPIOPin *GPIOPin::to_isr() const { - return new ISRInternalGPIOPin(this->pin_, + return new ISRInternalGPIOPin(this->pin_, // NOLINT #ifdef ARDUINO_ARCH_ESP32 this->gpio_clear_, this->gpio_set_, #endif diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index c5ff0102c3..53c7d1de43 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -141,21 +141,6 @@ std::string uint32_to_string(uint32_t num) { snprintf(buffer, sizeof(buffer), "%04X%04X", address16[1], address16[0]); return std::string(buffer); } -static char *global_json_build_buffer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static size_t global_json_build_buffer_size = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void reserve_global_json_build_buffer(size_t required_size) { - if (global_json_build_buffer_size == 0 || global_json_build_buffer_size < required_size) { - delete[] global_json_build_buffer; - global_json_build_buffer_size = std::max(required_size, global_json_build_buffer_size * 2); - - size_t remainder = global_json_build_buffer_size % 16U; - if (remainder != 0) - global_json_build_buffer_size += 16 - remainder; - - global_json_build_buffer = new char[global_json_build_buffer_size]; - } -} ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a63c627e5a..044de6c8b0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -338,7 +338,7 @@ template T *new_buffer(size_t length) { buffer = new T[length]; } #else - buffer = new T[length]; + buffer = new T[length]; // NOLINT #endif return buffer; diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp index bfc5d9f875..f051ce0e8a 100644 --- a/esphome/core/preferences.cpp +++ b/esphome/core/preferences.cpp @@ -17,13 +17,8 @@ namespace esphome { static const char *const TAG = "preferences"; -ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {} ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) - : offset_(offset), length_words_(length), type_(type) { - this->data_ = new uint32_t[this->length_words_ + 1]; // NOLINT(cppcoreguidelines-prefer-member-initializer) - for (uint32_t i = 0; i < this->length_words_ + 1; i++) - this->data_[i] = 0; -} + : offset_(offset), length_words_(length), type_(type), data_(length + 1) {} bool ESPPreferenceObject::load_() { if (!this->is_initialized()) { ESP_LOGV(TAG, "Load Pref Not initialized!"); @@ -173,7 +168,7 @@ ESPPreferences::ESPPreferences() : current_offset_(0) {} void ESPPreferences::begin() { - this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; + this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT ESP_LOGVV(TAG, "Loading preferences from flash..."); { @@ -234,7 +229,7 @@ bool ESPPreferenceObject::save_internal_() { char key[32]; sprintf(key, "%u", this->offset_); uint32_t len = (this->length_words_ + 1) * 4; - esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_, len); + esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_.data(), len); if (err) { ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); return false; @@ -264,7 +259,7 @@ bool ESPPreferenceObject::load_internal_() { ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len); return false; } - err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_, &len); + err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_.data(), &len); if (err) { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err)); return false; @@ -301,7 +296,7 @@ uint32_t ESPPreferenceObject::calculate_crc_() const { } return crc; } -bool ESPPreferenceObject::is_initialized() const { return this->data_ != nullptr; } +bool ESPPreferenceObject::is_initialized() const { return !this->data_.empty(); } ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 699871ca00..7ed9a2c8c5 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "esphome/core/defines.h" @@ -9,7 +11,7 @@ namespace esphome { class ESPPreferenceObject { public: - ESPPreferenceObject(); + ESPPreferenceObject() = default; ESPPreferenceObject(size_t offset, size_t length, uint32_t type); template bool save(T *src); @@ -28,12 +30,12 @@ class ESPPreferenceObject { uint32_t calculate_crc_() const; - size_t offset_; - size_t length_words_; - uint32_t type_; - uint32_t *data_; + size_t offset_ = 0; + size_t length_words_ = 0; + uint32_t type_ = 0; + std::vector data_; #ifdef ARDUINO_ARCH_ESP8266 - bool in_flash_{false}; + bool in_flash_ = false; #endif }; @@ -92,17 +94,18 @@ template ESPPreferenceObject ESPPreferences::make_preference(uint32_ template bool ESPPreferenceObject::save(T *src) { if (!this->is_initialized()) return false; - memset(this->data_, 0, this->length_words_ * 4); - memcpy(this->data_, src, sizeof(T)); + // ensure all bytes are 0 (in case sizeof(T) is not multiple of 4) + std::fill_n(data_.begin(), length_words_, 0); + memcpy(data_.data(), src, sizeof(T)); return this->save_(); } template bool ESPPreferenceObject::load(T *dest) { - memset(this->data_, 0, this->length_words_ * 4); + std::fill_n(data_.begin(), length_words_, 0); if (!this->load_()) return false; - memcpy(dest, this->data_, sizeof(T)); + memcpy(dest, data_.data(), sizeof(T)); return true; } diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index c3b192d15f..f9ba868096 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -13,23 +13,23 @@ using namespace esphome; void setup() { App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); - auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); + auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); // NOLINT log->pre_setup(); App.register_component(log); - auto *wifi = new wifi::WiFiComponent(); + auto *wifi = new wifi::WiFiComponent(); // NOLINT App.register_component(wifi); wifi::WiFiAP ap; ap.set_ssid("Test SSID"); ap.set_password("password1"); wifi->add_sta(ap); - auto *ota = new ota::OTAComponent(); + auto *ota = new ota::OTAComponent(); // NOLINT ota->set_port(8266); - auto *gpio = new gpio::GPIOSwitch(); + auto *gpio = new gpio::GPIOSwitch(); // NOLINT gpio->set_name("GPIO Switch"); - gpio->set_pin(new GPIOPin(8, OUTPUT, false)); + gpio->set_pin(new GPIOPin(8, OUTPUT, false)); // NOLINT App.register_component(gpio); App.register_switch(gpio); From aad03f1bf59e4e812109a6311422411cf748affd Mon Sep 17 00:00:00 2001 From: Tercio Filho Date: Mon, 13 Sep 2021 10:36:01 -0300 Subject: [PATCH 216/285] Fix issue #2054. PZEM004T Component doesn't set the module address. (#1784) --- esphome/components/pzem004t/pzem004t.cpp | 8 ++++++++ esphome/components/pzem004t/pzem004t.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/pzem004t/pzem004t.cpp b/esphome/components/pzem004t/pzem004t.cpp index 969ce8fa10..e5418765bd 100644 --- a/esphome/components/pzem004t/pzem004t.cpp +++ b/esphome/components/pzem004t/pzem004t.cpp @@ -6,6 +6,14 @@ namespace pzem004t { static const char *const TAG = "pzem004t"; +void PZEM004T::setup() { + // Clear UART buffer + while (this->available()) + this->read(); + // Set module address + this->write_state_(SET_ADDRESS); +} + void PZEM004T::loop() { const uint32_t now = millis(); if (now - this->last_read_ > 500 && this->available() < 7) { diff --git a/esphome/components/pzem004t/pzem004t.h b/esphome/components/pzem004t/pzem004t.h index 517b81eb21..f4f9f29b4d 100644 --- a/esphome/components/pzem004t/pzem004t.h +++ b/esphome/components/pzem004t/pzem004t.h @@ -14,6 +14,8 @@ class PZEM004T : public PollingComponent, public uart::UARTDevice { void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } + void setup() override; + void loop() override; void update() override; From fe47ddc27a295da15e8c6df9c262ba1fba5d27e0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 16:39:35 +0200 Subject: [PATCH 217/285] Convert st7735.h to use LF line endings (#2287) --- esphome/components/st7735/st7735.h | 174 ++++++++++++++--------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 737170e99b..0eb4ccadd8 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -1,87 +1,87 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/spi/spi.h" -#include "esphome/components/display/display_buffer.h" - -namespace esphome { -namespace st7735 { - -static const uint8_t ST7735_TFTWIDTH_128 = 128; // for 1.44 and mini^M -static const uint8_t ST7735_TFTWIDTH_80 = 80; // for mini^M -static const uint8_t ST7735_TFTHEIGHT_128 = 128; // for 1.44" display^M -static const uint8_t ST7735_TFTHEIGHT_160 = 160; // for 1.8" and mini display^M - -// some flags for initR() :( -static const uint8_t INITR_GREENTAB = 0x00; -static const uint8_t INITR_REDTAB = 0x01; -static const uint8_t INITR_BLACKTAB = 0x02; -static const uint8_t INITR_144GREENTAB = 0x01; -static const uint8_t INITR_MINI_160X80 = 0x04; -static const uint8_t INITR_HALLOWING = 0x05; -static const uint8_t INITR_18GREENTAB = INITR_GREENTAB; -static const uint8_t INITR_18REDTAB = INITR_REDTAB; -static const uint8_t INITR_18BLACKTAB = INITR_BLACKTAB; - -enum ST7735Model { - ST7735_INITR_GREENTAB = INITR_GREENTAB, - ST7735_INITR_REDTAB = INITR_REDTAB, - ST7735_INITR_BLACKTAB = INITR_BLACKTAB, - ST7735_INITR_MINI_160X80 = INITR_MINI_160X80, - ST7735_INITR_18BLACKTAB = INITR_18BLACKTAB, - ST7735_INITR_18REDTAB = INITR_18REDTAB -}; - -class ST7735 : public PollingComponent, - public display::DisplayBuffer, - public spi::SPIDevice { - public: - ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); - void dump_config() override; - void setup() override; - - void display(); - - void update() override; - - void set_model(ST7735Model model) { this->model_ = model; } - float get_setup_priority() const override { return setup_priority::PROCESSOR; } - - void set_reset_pin(GPIOPin *value) { this->reset_pin_ = value; } - void set_dc_pin(GPIOPin *value) { dc_pin_ = value; } - size_t get_buffer_length(); - - protected: - void sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes); - void senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes); - - void writecommand_(uint8_t value); - void writedata_(uint8_t value); - - void write_display_data_(); - - void init_reset_(); - void display_init_(const uint8_t *addr); - void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); - void draw_absolute_pixel_internal(int x, int y, Color color) override; - void spi_master_write_addr_(uint16_t addr1, uint16_t addr2); - void spi_master_write_color_(uint16_t color, uint16_t size); - - int get_width_internal() override; - int get_height_internal() override; - - const char *model_str_(); - - ST7735Model model_{ST7735_INITR_18BLACKTAB}; - uint8_t colstart_ = 0, rowstart_ = 0; - bool eightbitcolor_ = false; - bool usebgr_ = false; - int16_t width_ = 80, height_ = 80; // Watch heap size - - GPIOPin *reset_pin_{nullptr}; - GPIOPin *dc_pin_{nullptr}; -}; - -} // namespace st7735 -} // namespace esphome +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace st7735 { + +static const uint8_t ST7735_TFTWIDTH_128 = 128; // for 1.44 and mini^M +static const uint8_t ST7735_TFTWIDTH_80 = 80; // for mini^M +static const uint8_t ST7735_TFTHEIGHT_128 = 128; // for 1.44" display^M +static const uint8_t ST7735_TFTHEIGHT_160 = 160; // for 1.8" and mini display^M + +// some flags for initR() :( +static const uint8_t INITR_GREENTAB = 0x00; +static const uint8_t INITR_REDTAB = 0x01; +static const uint8_t INITR_BLACKTAB = 0x02; +static const uint8_t INITR_144GREENTAB = 0x01; +static const uint8_t INITR_MINI_160X80 = 0x04; +static const uint8_t INITR_HALLOWING = 0x05; +static const uint8_t INITR_18GREENTAB = INITR_GREENTAB; +static const uint8_t INITR_18REDTAB = INITR_REDTAB; +static const uint8_t INITR_18BLACKTAB = INITR_BLACKTAB; + +enum ST7735Model { + ST7735_INITR_GREENTAB = INITR_GREENTAB, + ST7735_INITR_REDTAB = INITR_REDTAB, + ST7735_INITR_BLACKTAB = INITR_BLACKTAB, + ST7735_INITR_MINI_160X80 = INITR_MINI_160X80, + ST7735_INITR_18BLACKTAB = INITR_18BLACKTAB, + ST7735_INITR_18REDTAB = INITR_18REDTAB +}; + +class ST7735 : public PollingComponent, + public display::DisplayBuffer, + public spi::SPIDevice { + public: + ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); + void dump_config() override; + void setup() override; + + void display(); + + void update() override; + + void set_model(ST7735Model model) { this->model_ = model; } + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + + void set_reset_pin(GPIOPin *value) { this->reset_pin_ = value; } + void set_dc_pin(GPIOPin *value) { dc_pin_ = value; } + size_t get_buffer_length(); + + protected: + void sendcommand_(uint8_t cmd, const uint8_t *data_bytes, uint8_t num_data_bytes); + void senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes); + + void writecommand_(uint8_t value); + void writedata_(uint8_t value); + + void write_display_data_(); + + void init_reset_(); + void display_init_(const uint8_t *addr); + void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void spi_master_write_addr_(uint16_t addr1, uint16_t addr2); + void spi_master_write_color_(uint16_t color, uint16_t size); + + int get_width_internal() override; + int get_height_internal() override; + + const char *model_str_(); + + ST7735Model model_{ST7735_INITR_18BLACKTAB}; + uint8_t colstart_ = 0, rowstart_ = 0; + bool eightbitcolor_ = false; + bool usebgr_ = false; + int16_t width_ = 80, height_ = 80; // Watch heap size + + GPIOPin *reset_pin_{nullptr}; + GPIOPin *dc_pin_{nullptr}; +}; + +} // namespace st7735 +} // namespace esphome From 133a17d6eb3b8b94d6bc0d6d3313d7f7103d3585 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 16:55:01 +0200 Subject: [PATCH 218/285] Add esphal.h include to inkplate6 component (#2286) --- esphome/components/inkplate6/inkplate.h | 1 + tests/test5.yaml | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 94d14b4f6e..f2821b22a9 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/esphal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/display/display_buffer.h" diff --git a/tests/test5.yaml b/tests/test5.yaml index 5d4ff025d9..84d4a5a44e 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -28,6 +28,8 @@ uart: rx_pin: 16 baud_rate: 19200 +i2c: + modbus: uart_id: uart1 @@ -149,3 +151,20 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor + +display: + - platform: inkplate6 + id: inkplate_display + greyscale: false + partial_updating: false + update_interval: 60s + + ckv_pin: GPIO1 + sph_pin: GPIO1 + gmod_pin: GPIO1 + gpio0_enable_pin: GPIO1 + oe_pin: GPIO1 + spv_pin: GPIO1 + powerup_pin: GPIO1 + wakeup_pin: GPIO1 + vcom_pin: GPIO1 From a2d2863c72edf6d5f04a8291fdb5a47349149d63 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 17:22:24 +0200 Subject: [PATCH 219/285] Revert "Bump tzlocal from 2.1 to 3.0 (#2154)" (#2289) This reverts commit e0cff214b215ab2cd4dfb221b37d0f56dbce141b. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7de02d8ccb..daaf86e641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 protobuf==3.17.3 -tzlocal==3.0 +tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 From 40c474cd832f736b191361e01a20165fbb0fc36b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:11:27 +0200 Subject: [PATCH 220/285] Run clang-tidy against ESP32 (#2147) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Otto winter --- .clang-tidy | 2 +- .github/workflows/ci.yml | 21 ++++++---- esphome/components/ac_dimmer/ac_dimmer.cpp | 2 +- .../airthings_ble/airthings_listener.cpp | 2 +- .../airthings_wave_plus.cpp | 30 ++++++------- .../airthings_wave_plus/airthings_wave_plus.h | 32 +++++++------- esphome/components/am43/am43.cpp | 6 +-- esphome/components/am43/am43.h | 4 +- esphome/components/am43/cover/am43_cover.cpp | 6 +-- esphome/components/am43/cover/am43_cover.h | 4 +- esphome/components/anova/anova.cpp | 6 +-- esphome/components/anova/anova.h | 2 +- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_server.cpp | 11 ++--- .../atc_mithermometer/atc_mithermometer.cpp | 12 ++---- .../atc_mithermometer/atc_mithermometer.h | 1 - esphome/components/ble_client/ble_client.cpp | 16 +++---- esphome/components/esp32_ble/ble.cpp | 18 ++++---- .../components/esp32_ble/ble_advertising.cpp | 1 + esphome/components/esp32_ble/ble_uuid.cpp | 42 +++++++++---------- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 7 ++-- .../esp32_ble_server/ble_characteristic.cpp | 2 +- .../esp32_ble_server/ble_descriptor.cpp | 4 +- .../esp32_ble_server/ble_server.cpp | 4 +- .../esp32_ble_server/ble_service.cpp | 3 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 12 +++--- .../components/esp32_camera/esp32_camera.cpp | 4 +- .../esp32_improv/esp32_improv_component.cpp | 10 ++--- .../components/esp8266_pwm/esp8266_pwm.cpp | 4 ++ esphome/components/esp8266_pwm/esp8266_pwm.h | 4 ++ .../ethernet/ethernet_component.cpp | 8 ++-- .../components/ethernet/ethernet_component.h | 2 +- esphome/components/i2c/i2c.cpp | 11 ++--- esphome/components/i2c/i2c.h | 4 -- .../inkbird_ibsth1_mini.cpp | 24 +++++------ esphome/components/inkplate6/inkplate.cpp | 16 ++++--- esphome/components/ledc/ledc_output.cpp | 2 +- esphome/components/mqtt/mqtt_client.cpp | 4 +- esphome/components/nextion/nextion_upload.cpp | 8 +--- .../pulse_counter/pulse_counter_sensor.cpp | 7 +--- .../pulse_counter/pulse_counter_sensor.h | 4 -- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 14 ++----- .../pvvx_mithermometer/pvvx_mithermometer.h | 1 - .../components/remote_base/rc5_protocol.cpp | 6 +-- .../components/socket/bsd_sockets_impl.cpp | 8 ++-- esphome/components/spi/spi.cpp | 2 +- esphome/components/st7735/st7735.cpp | 20 ++++----- esphome/components/st7789v/st7789v.cpp | 20 ++++----- esphome/components/uart/uart.h | 4 -- esphome/components/uart/uart_esp32.cpp | 6 +-- .../components/wifi/wifi_component_esp32.cpp | 36 ++++++++-------- .../wifi/wifi_component_esp8266.cpp | 6 +-- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 16 +++---- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 8 +--- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 8 +--- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 8 +--- .../components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 10 ++--- .../xiaomi_gcls002/xiaomi_gcls002.cpp | 6 +-- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 6 +-- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 6 +-- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 6 +-- .../xiaomi_lywsd02/xiaomi_lywsd02.cpp | 6 +-- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 8 +--- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 6 +-- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 8 +--- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 10 ++--- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 8 +--- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 6 +-- .../xiaomi_wx08zm/xiaomi_wx08zm.cpp | 6 +-- esphome/core/application.cpp | 6 +-- esphome/core/defines.h | 5 +-- platformio.ini | 1 - script/clang-tidy | 14 +++++-- script/helpers.py | 10 +++++ 74 files changed, 291 insertions(+), 364 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 597065d50b..8451a2bdd4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -124,7 +124,7 @@ CheckOptions: - key: readability-identifier-naming.StaticConstantCase value: 'UPPER_CASE' - key: readability-identifier-naming.StaticVariableCase - value: 'UPPER_CASE' + value: 'lower_case' - key: readability-identifier-naming.GlobalConstantCase value: 'UPPER_CASE' - key: readability-identifier-naming.ParameterCase diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a841f84c..eb93c36d19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,20 @@ jobs: - id: clang-format name: Run script/clang-format - id: clang-tidy - name: Run script/clang-tidy 1/4 - split: 1 + name: Run script/clang-tidy for ESP8266 + options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 - id: clang-tidy - name: Run script/clang-tidy 2/4 - split: 2 + name: Run script/clang-tidy for ESP32 1/4 + options: --environment esp32-tidy --split-num 4 --split-at 1 - id: clang-tidy - name: Run script/clang-tidy 3/4 - split: 3 + name: Run script/clang-tidy for ESP32 2/4 + options: --environment esp32-tidy --split-num 4 --split-at 2 - id: clang-tidy - name: Run script/clang-tidy 4/4 - split: 4 + name: Run script/clang-tidy for ESP32 3/4 + options: --environment esp32-tidy --split-num 4 --split-at 3 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 4/4 + options: --environment esp32-tidy --split-num 4 --split-at 4 # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed @@ -51,7 +54,7 @@ jobs: if: ${{ matrix.id == 'clang-format' }} - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + run: script/clang-tidy --all-headers --fix ${{ matrix.options }} if: ${{ matrix.id == 'clang-tidy' }} - name: Suggested changes diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 7e9251ddf3..9a71f0e224 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -141,7 +141,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store #ifdef ARDUINO_ARCH_ESP32 // ESP32 implementation, uses basically the same code but needs to wrap // timer_interrupt() function to auto-reschedule -static hw_timer_t *dimmer_timer = nullptr; +static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } #endif diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index 921e42c498..a843bcb145 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace airthings_ble { -static const char *TAG = "airthings_ble"; +static const char *const TAG = "airthings_ble"; bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { for (auto &it : device.get_manufacturer_datas()) { diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 6b2e807e0b..6b4780726d 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -5,6 +5,8 @@ namespace esphome { namespace airthings_wave_plus { +static const char *const TAG = "airthings_wave_plus"; + void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { @@ -21,15 +23,15 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_SEARCH_CMPL_EVT: { - this->handle = 0; - auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); + this->handle_ = 0; + auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), - sensors_data_characteristic_uuid.to_string().c_str()); + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_data_characteristic_uuid_.to_string().c_str()); break; } - this->handle = chr->handle; - this->node_state = espbt::ClientState::Established; + this->handle_ = chr->handle; + this->node_state = esp32_ble_tracker::ClientState::Established; request_read_values_(); break; @@ -42,7 +44,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); break; } - if (param->read.handle == this->handle) { + if (param->read.handle == this->handle_) { read_sensors_(param->read.value, param->read.value_len); } break; @@ -88,16 +90,16 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { } } -bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } +bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } -bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } +bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } -bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } +bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } void AirthingsWavePlus::loop() {} void AirthingsWavePlus::update() { - if (this->node_state != espbt::ClientState::Established) { + if (this->node_state != esp32_ble_tracker::ClientState::Established) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); @@ -110,7 +112,7 @@ void AirthingsWavePlus::update() { void AirthingsWavePlus::request_read_values_() { auto status = - esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); } @@ -130,8 +132,8 @@ AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); - service_uuid = espbt::ESPBTUUID::from_uuid(service_bt); - sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt); + service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(service_bt); + sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_uuid(characteristic_bt); } void AirthingsWavePlus::setup() {} diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 18d7fe60d2..88d7a7a01b 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,25 +1,21 @@ #pragma once +#ifdef ARDUINO_ARCH_ESP32 + +#include +#include +#include +#include #include "esphome/core/component.h" +#include "esphome/core/log.h" #include "esphome/components/ble_client/ble_client.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/core/log.h" -#include -#include - -#ifdef ARDUINO_ARCH_ESP32 -#include -#include - -using namespace esphome::ble_client; namespace esphome { namespace airthings_wave_plus { -static const char *TAG = "airthings_wave_plus"; - -class AirthingsWavePlus : public PollingComponent, public BLEClientNode { +class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWavePlus(); @@ -40,9 +36,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode { void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } protected: - bool is_valid_radon_value_(short radon); - bool is_valid_voc_value_(short voc); - bool is_valid_co2_value_(short co2); + bool is_valid_radon_value_(uint16_t radon); + bool is_valid_voc_value_(uint16_t voc); + bool is_valid_co2_value_(uint16_t co2); void read_sensors_(uint8_t *value, uint16_t value_len); void request_read_values_(); @@ -55,9 +51,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode { sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr}; - uint16_t handle; - espbt::ESPBTUUID service_uuid; - espbt::ESPBTUUID sensors_data_characteristic_uuid; + uint16_t handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; struct WavePlusReadings { uint8_t version; diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index e60bb2474c..94be194a1d 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace am43 { -static const char *TAG = "am43"; +static const char *const TAG = "am43"; void Am43::dump_config() { ESP_LOGCONFIG(TAG, "AM43"); @@ -15,8 +15,8 @@ void Am43::dump_config() { } void Am43::setup() { - this->encoder_ = new Am43Encoder(); - this->decoder_ = new Am43Decoder(); + this->encoder_ = make_unique(); + this->decoder_ = make_unique(); this->logged_in_ = false; this->last_battery_update_ = 0; this->current_sensor_ = 0; diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h index 460bb601b2..025d075b68 100644 --- a/esphome/components/am43/am43.h +++ b/esphome/components/am43/am43.h @@ -28,8 +28,8 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent protected: uint16_t char_handle_; - Am43Encoder *encoder_; - Am43Decoder *decoder_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; bool logged_in_; sensor::Sensor *battery_{nullptr}; sensor::Sensor *illuminance_{nullptr}; diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 3ae7fe8f8c..1067ce9ebb 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace am43 { -static const char *TAG = "am43_cover"; +static const char *const TAG = "am43_cover"; using namespace esphome::cover; @@ -18,8 +18,8 @@ void Am43Component::dump_config() { void Am43Component::setup() { this->position = COVER_OPEN; - this->encoder_ = new Am43Encoder(); - this->decoder_ = new Am43Decoder(); + this->encoder_ = make_unique(); + this->decoder_ = make_unique(); this->logged_in_ = false; } diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h index 5557da49e7..dba1391b63 100644 --- a/esphome/components/am43/cover/am43_cover.h +++ b/esphome/components/am43/cover/am43_cover.h @@ -32,8 +32,8 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient uint16_t char_handle_; uint16_t pin_; bool invert_position_; - Am43Encoder *encoder_; - Am43Decoder *decoder_; + std::unique_ptr encoder_; + std::unique_ptr decoder_; bool logged_in_; float position_; diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index f330969c33..d0ea818669 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -6,14 +6,14 @@ namespace esphome { namespace anova { -static const char *TAG = "anova"; +static const char *const TAG = "anova"; using namespace esphome::climate; void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); } void Anova::setup() { - this->codec_ = new AnovaCodec(); + this->codec_ = make_unique(); this->current_request_ = 0; } @@ -135,7 +135,7 @@ void Anova::update() { if (this->current_request_ < 2) { auto pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) - auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); + this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 42bdbcaed0..e033513013 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -39,7 +39,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode void set_unit_of_measurement(const char *); protected: - AnovaCodec *codec_; + std::unique_ptr codec_; void control(const climate::ClimateCall &call) override; uint16_t char_handle_; uint8_t current_request_; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ea3be42275..a530554155 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -633,7 +633,7 @@ void APIConnection::send_camera_state(std::shared_ptr return; if (this->image_reader_.available()) return; - this->image_reader_.set_image(image); + this->image_reader_.set_image(std::move(image)); } bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { ListEntitiesCameraResponse msg; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 70f82c59db..1ef567ab57 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -76,11 +76,12 @@ void APIServer::setup() { #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr) { - esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { - for (auto &c : this->clients_) - if (!c->remove_) - c->send_camera_state(image); - }); + esp32_camera::global_esp32_camera->add_image_callback( + [this](const std::shared_ptr &image) { + for (auto &c : this->clients_) + if (!c->remove_) + c->send_camera_state(image); + }); } #endif } diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 5656cdf430..1dba259143 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -26,7 +26,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device bool success = false; for (auto &service_data : device.get_service_datas()) { auto res = parse_header(service_data); - if (res->is_duplicate) { + if (!res.has_value()) { continue; } if (!(parse_message(service_data.data, *res))) { @@ -46,11 +46,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } optional ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { @@ -64,12 +60,10 @@ optional ATCMiThermometer::parse_header(const esp32_ble_tracker::Se static uint8_t last_frame_count = 0; if (last_frame_count == raw[12]) { - ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast(last_frame_count)); - result.is_duplicate = true; + ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count); return {}; } last_frame_count = raw[12]; - result.is_duplicate = false; return result; } diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index 203dca3200..eff14811be 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -14,7 +14,6 @@ struct ParseResult { optional humidity; optional battery_level; optional battery_voltage; - bool is_duplicate; int raw_offset; }; diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 956848b73d..0398113f83 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -130,7 +130,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es break; } ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL); + esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); break; } case ESP_GATTC_DISCONNECT_EVT: { @@ -139,13 +139,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es } ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str()); for (auto &svc : this->services_) - delete svc; + delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); this->set_states(espbt::ClientState::Idle); break; } case ESP_GATTC_SEARCH_RES_EVT: { - BLEService *ble_service = new BLEService(); + BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory) ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); ble_service->start_handle = param->search_res.start_handle; ble_service->end_handle = param->search_res.end_handle; @@ -194,7 +194,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es // Delete characteristics after clients have used them to save RAM. if (!all_established && this->all_nodes_established()) { for (auto &svc : this->services_) - delete svc; + delete svc; // NOLINT(cppcoreguidelines-owning-memory) this->services_.clear(); } } @@ -307,7 +307,7 @@ BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_ BLEService::~BLEService() { for (auto &chr : this->characteristics) - delete chr; + delete chr; // NOLINT(cppcoreguidelines-owning-memory) } void BLEService::parse_characteristics() { @@ -329,7 +329,7 @@ void BLEService::parse_characteristics() { break; } - BLECharacteristic *characteristic = new BLECharacteristic(); + BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory) characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); characteristic->properties = result.properties; characteristic->handle = result.char_handle; @@ -344,7 +344,7 @@ void BLEService::parse_characteristics() { BLECharacteristic::~BLECharacteristic() { for (auto &desc : this->descriptors) - delete desc; + delete desc; // NOLINT(cppcoreguidelines-owning-memory) } void BLECharacteristic::parse_descriptors() { @@ -366,7 +366,7 @@ void BLECharacteristic::parse_descriptors() { break; } - BLEDescriptor *desc = new BLEDescriptor(); + BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory) desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); desc->handle = result.handle; desc->characteristic = this; diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a54ee48b30..2d710a6ef7 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -27,7 +27,7 @@ void ESP32BLE::setup() { return; } - this->advertising_ = new BLEAdvertising(); + this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) this->advertising_->set_scan_response(true); this->advertising_->set_min_preferred_interval(0x06); @@ -123,25 +123,25 @@ void ESP32BLE::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { switch (ble_event->type_) { - case ble_event->GATTS: + case BLEEvent::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: + case BLEEvent::GAP: this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); break; default: break; } - delete ble_event; + delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) 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) { - BLEEvent *new_event = new BLEEvent(event, param); + BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) global_ble->ble_events_.push(new_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); @@ -153,9 +153,9 @@ 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) { - BLEEvent *new_event = new BLEEvent(event, gatts_if, param); + BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory) global_ble->ble_events_.push(new_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -174,7 +174,7 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); } -ESP32BLE *global_ble = nullptr; +ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble } // namespace esphome diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 92270124dd..bcfb0d1a1b 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -45,6 +45,7 @@ void BLEAdvertising::start() { this->advertising_data_.service_uuid_len = 0; } else { this->advertising_data_.service_uuid_len = 16 * num_services; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) this->advertising_data_.p_service_uuid = new uint8_t[this->advertising_data_.service_uuid_len]; uint8_t *p = this->advertising_data_.p_service_uuid; for (int i = 0; i < num_services; i++) { diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 0de938d9a8..b33275110d 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -33,28 +33,28 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = 0; for (int i = 0; i < data.length();) { - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid16 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (2 - i) * 4; + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4; i += 2; } } else if (data.length() == 8) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = 0; for (int i = 0; i < data.length();) { - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid32 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (6 - i) * 4; + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4; i += 2; } } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be @@ -69,14 +69,14 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { for (int i = 0; i < data.length();) { if (data.c_str()[i] == '-') i++; - uint8_t MSB = data.c_str()[i]; - uint8_t LSB = data.c_str()[i + 1]; + uint8_t msb = data.c_str()[i]; + uint8_t lsb = data.c_str()[i + 1]; - if (MSB > '9') - MSB -= 7; - if (LSB > '9') - LSB -= 7; - ret.uuid_.uuid.uuid128[15 - n++] = ((MSB & 0x0F) << 4) | (LSB & 0x0F); + if (msb > '9') + msb -= 7; + if (lsb > '9') + lsb -= 7; + ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); i += 2; } } else { diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 1b06fd787e..c9e00a1093 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -15,6 +15,7 @@ namespace esp32_ble_beacon { static const char *const TAG = "esp32_ble_beacon"; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static esp_ble_adv_params_t ble_adv_params = { .adv_int_min = 0x20, .adv_int_max = 0x40, @@ -28,7 +29,7 @@ static esp_ble_adv_params_t ble_adv_params = { #define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8)) -static esp_ble_ibeacon_head_t ibeacon_common_head = { +static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502}; void ESP32BLEBeacon::dump_config() { @@ -90,7 +91,7 @@ void ESP32BLEBeacon::ble_setup() { } esp_ble_ibeacon_t ibeacon_adv_data; - memcpy(&ibeacon_adv_data.ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t)); + memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); @@ -131,7 +132,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap } } -ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; +ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble_beacon } // namespace esphome diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 62775ab05f..d8ff39cc94 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -27,7 +27,7 @@ BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) void BLECharacteristic::set_value(std::vector value) { xSemaphoreTake(this->set_value_lock_, 0L); - this->value_ = value; + this->value_ = std::move(value); xSemaphoreGive(this->set_value_lock_); } void BLECharacteristic::set_value(const std::string &value) { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index a04343da3a..5262087ad1 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -15,10 +15,10 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->uuid_ = uuid; this->value_.attr_len = 0; this->value_.attr_max_len = max_len; - this->value_.attr_value = (uint8_t *) malloc(max_len); + this->value_.attr_value = (uint8_t *) malloc(max_len); // NOLINT } -BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } +BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } // NOLINT void BLEDescriptor::do_create(BLECharacteristic *characteristic) { this->characteristic_ = characteristic; diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index f1eebf3c8a..5777e99e23 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -109,7 +109,7 @@ BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) { } BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str()); - BLEService *service = new BLEService(uuid, num_handles, inst_id); + BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory) this->services_.push_back(service); if (advertise) { esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); @@ -158,7 +158,7 @@ float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } -BLEServer *global_ble_server = nullptr; +BLEServer *global_ble_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_ble_server } // namespace esphome diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index b7c88a436c..5cfc53a397 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -14,7 +14,7 @@ BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) BLEService::~BLEService() { for (auto &chr : this->characteristics_) - delete chr; + delete chr; // NOLINT(cppcoreguidelines-owning-memory) } BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) { @@ -34,6 +34,7 @@ BLECharacteristic *BLEService::create_characteristic(const std::string &uuid, es return create_characteristic(ESPBTUUID::from_raw(uuid), properties); } BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_char_prop_t properties) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) BLECharacteristic *characteristic = new BLECharacteristic(uuid, properties); this->characteristics_.push_back(characteristic); return characteristic; diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index e1cd3975e8..358fe50605 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -21,7 +21,7 @@ namespace esp32_ble_tracker { static const char *const TAG = "esp32_ble_tracker"; -ESP32BLETracker *global_esp32_ble_tracker = nullptr; +ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { uint64_t u = 0; @@ -55,7 +55,7 @@ void ESP32BLETracker::loop() { &ble_event->event_.gattc.gattc_param); else this->real_gap_event_handler(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); - delete ble_event; + delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } @@ -204,9 +204,9 @@ void ESP32BLETracker::register_client(ESPBTClient *client) { } void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - BLEEvent *gap_event = new BLEEvent(event, param); + BLEEvent *gap_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) global_esp32_ble_tracker->ble_events_.push(gap_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLETracker::real_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { @@ -254,9 +254,9 @@ void ESP32BLETracker::gap_scan_result(const esp_ble_gap_cb_param_t::ble_scan_res void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); + BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory) global_esp32_ble_tracker->ble_events_.push(gattc_event); -} +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) void ESP32BLETracker::real_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 500ddd67c9..072cf41460 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -275,10 +275,10 @@ void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { } void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } -ESP32Camera *global_esp32_camera; +ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void CameraImageReader::set_image(std::shared_ptr image) { - this->image_ = image; + this->image_ = std::move(image); this->offset_ = 0; } size_t CameraImageReader::available() const { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 65d253078c..b584d2e8b9 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -32,7 +32,7 @@ void ESP32ImprovComponent::setup_characteristics() { this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_->on_write([this](const std::vector &data) { - if (data.size() > 0) { + if (!data.empty()) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } }); @@ -56,7 +56,7 @@ void ESP32ImprovComponent::setup_characteristics() { } void ESP32ImprovComponent::loop() { - if (this->incoming_data_.size() > 0) + if (!this->incoming_data_.empty()) this->process_incoming_data_(); uint32_t now = millis(); @@ -162,7 +162,7 @@ bool ESP32ImprovComponent::check_identify_() { void ESP32ImprovComponent::set_state_(improv::State state) { ESP_LOGV(TAG, "Setting state: %d", state); this->state_ = state; - if (this->status_->get_value().size() == 0 || this->status_->get_value()[0] != state) { + if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) { uint8_t data[1]{state}; this->status_->set_value(data, 1); if (state != improv::STATE_STOPPED) @@ -173,7 +173,7 @@ void ESP32ImprovComponent::set_state_(improv::State state) { void ESP32ImprovComponent::set_error_(improv::Error error) { if (error != improv::ERROR_NONE) ESP_LOGE(TAG, "Error: %d", error); - if (this->error_->get_value().size() == 0 || this->error_->get_value()[0] != error) { + if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) { uint8_t data[1]{error}; this->error_->set_value(data, 1); if (this->state_ != improv::STATE_STOPPED) @@ -274,7 +274,7 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() { void ESP32ImprovComponent::on_client_disconnect() { this->set_error_(improv::ERROR_NONE); }; -ESP32ImprovComponent *global_improv_component = nullptr; +ESP32ImprovComponent *global_improv_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esp32_improv } // namespace esphome diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index d725e90edc..186653acd1 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,3 +1,5 @@ +#ifdef ARDUINO_ARCH_ESP8266 + #include "esp8266_pwm.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" @@ -55,3 +57,5 @@ void HOT ESP8266PWM::write_state(float state) { } // namespace esp8266_pwm } // namespace esphome + +#endif diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.h b/esphome/components/esp8266_pwm/esp8266_pwm.h index 661db6611f..1bf875dc43 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.h +++ b/esphome/components/esp8266_pwm/esp8266_pwm.h @@ -1,5 +1,7 @@ #pragma once +#ifdef ARDUINO_ARCH_ESP8266 + #include "esphome/core/component.h" #include "esphome/core/esphal.h" #include "esphome/core/automation.h" @@ -48,3 +50,5 @@ template class SetFrequencyAction : public Action { } // namespace esp8266_pwm } // namespace esphome + +#endif diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index b22f8c97d1..cad76ae476 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -16,17 +16,17 @@ // Defined in WiFiGeneric.cpp, sets global initialized flag, starts network event task queue and calls // tcpip_adapter_init() -extern void tcpipInit(); +extern void tcpipInit(); // NOLINT(readability-identifier-naming) namespace esphome { namespace ethernet { static const char *const TAG = "ethernet"; -EthernetComponent *global_eth_component; +EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ + if ((err) != ESP_OK) { \ ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ this->mark_failed(); \ return; \ @@ -258,7 +258,7 @@ void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_clk_mode(eth_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } -void EthernetComponent::set_manual_ip(ManualIP manual_ip) { this->manual_ip_ = manual_ip; } +void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } std::string EthernetComponent::get_use_address() const { if (this->use_address_.empty()) { return App.get_name() + ".local"; diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 326cd1edea..dc8816ef86 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -48,7 +48,7 @@ class EthernetComponent : public Component { void set_mdio_pin(uint8_t mdio_pin); void set_type(EthernetType type); void set_clk_mode(eth_clock_mode_t clk_mode); - void set_manual_ip(ManualIP manual_ip); + void set_manual_ip(const ManualIP &manual_ip); IPAddress get_ip_address(); std::string get_use_address() const; diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index f1980e1f1d..a3a3f7f82a 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -10,11 +10,12 @@ static const char *const TAG = "i2c"; I2CComponent::I2CComponent() { #ifdef ARDUINO_ARCH_ESP32 - if (next_i2c_bus_num_ == 0) + static uint8_t next_i2c_bus_num = 0; + if (next_i2c_bus_num == 0) this->wire_ = &Wire; else - this->wire_ = new TwoWire(next_i2c_bus_num_); - next_i2c_bus_num_++; + this->wire_ = new TwoWire(next_i2c_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + next_i2c_bus_num++; #else this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) #endif @@ -273,10 +274,6 @@ bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT } void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } -#ifdef ARDUINO_ARCH_ESP32 -uint8_t next_i2c_bus_num_ = 0; -#endif - I2CRegister &I2CRegister::operator=(uint8_t value) { this->parent_->write_byte(this->register_, value); return *this; diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index da791ec633..6f93d4c0e4 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -131,10 +131,6 @@ class I2CComponent : public Component { bool scan_; }; -#ifdef ARDUINO_ARCH_ESP32 -extern uint8_t next_i2c_bus_num_; -#endif - class I2CDevice; class I2CMultiplexer; class I2CRegister { diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index a940a77148..81693c6d96 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -37,25 +37,25 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi ESP_LOGVV(TAG, "parse_device(): address is not public"); return false; } - if (device.get_service_datas().size() != 0) { + if (!device.get_service_datas().empty()) { ESP_LOGVV(TAG, "parse_device(): service_data is expected to be empty"); return false; } - auto mnfDatas = device.get_manufacturer_datas(); - if (mnfDatas.size() != 1) { + auto mnf_datas = device.get_manufacturer_datas(); + if (mnf_datas.size() != 1) { ESP_LOGVV(TAG, "parse_device(): manufacturer_datas is expected to have a single element"); return false; } - auto mnfData = mnfDatas[0]; - if (mnfData.uuid.get_uuid().len != ESP_UUID_LEN_16) { + auto mnf_data = mnf_datas[0]; + if (mnf_data.uuid.get_uuid().len != ESP_UUID_LEN_16) { ESP_LOGVV(TAG, "parse_device(): manufacturer data element is expected to have uuid of length 16"); return false; } - if (mnfData.data.size() != 7) { + if (mnf_data.data.size() != 7) { ESP_LOGVV(TAG, "parse_device(): manufacturer data element length is expected to be of length 7"); return false; } - if (mnfData.data[6] != 8) { + if (mnf_data.data[6] != 8) { ESP_LOGVV(TAG, "parse_device(): unexpected data"); return false; } @@ -72,20 +72,20 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi auto external_temperature = NAN; // Read bluetooth data into variable - auto measured_temperature = mnfData.uuid.get_uuid().uuid.uuid16 / 100.0f; + auto measured_temperature = mnf_data.uuid.get_uuid().uuid.uuid16 / 100.0f; // Set temperature or external_temperature based on which sensor is in use - if (mnfData.data[2] == 0) { + if (mnf_data.data[2] == 0) { temperature = measured_temperature; - } else if (mnfData.data[2] == 1) { + } else if (mnf_data.data[2] == 1) { external_temperature = measured_temperature; } else { ESP_LOGVV(TAG, "parse_device(): unknown sensor type"); return false; } - auto battery_level = mnfData.data[5]; - auto humidity = ((mnfData.data[1] << 8) + mnfData.data[0]) / 100.0f; + auto battery_level = mnf_data.data[5]; + auto humidity = ((mnf_data.data[1] << 8) + mnf_data.data[0]) / 100.0f; // Send temperature only if the value is set if (!isnan(temperature) && this->temperature_ != nullptr) { diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 089b4791a6..b5cec6cf9e 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -150,7 +150,6 @@ void Inkplate6::dump_config() { } void Inkplate6::eink_off_() { ESP_LOGV(TAG, "Eink off called"); - unsigned long start_time = millis(); if (panel_on_ == 0) return; panel_on_ = 0; @@ -170,7 +169,6 @@ void Inkplate6::eink_off_() { } void Inkplate6::eink_on_() { ESP_LOGV(TAG, "Eink on called"); - unsigned long start_time = millis(); if (panel_on_ == 1) return; panel_on_ = 1; @@ -200,7 +198,7 @@ void Inkplate6::eink_on_() { } void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) { uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; @@ -214,7 +212,7 @@ void Inkplate6::fill(Color color) { } void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) { this->display3b_(); @@ -229,7 +227,7 @@ void Inkplate6::display() { } void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); @@ -348,7 +346,7 @@ void Inkplate6::display1b_() { } void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); clean_fast_(0, 1); @@ -424,7 +422,7 @@ void Inkplate6::display3b_() { } bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); if (this->greyscale_) return false; if (this->block_partial_) @@ -531,7 +529,7 @@ void Inkplate6::vscan_end_() { } void Inkplate6::clean() { ESP_LOGV(TAG, "Clean called"); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); clean_fast_(0, 1); // White @@ -544,7 +542,7 @@ void Inkplate6::clean() { } void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); - unsigned long start_time = millis(); + uint32_t start_time = millis(); eink_on_(); uint8_t data = 0; diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 0575dbee6a..45bee5b871 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -63,7 +63,7 @@ void LEDCOutput::update_frequency(float frequency) { this->write_state(this->duty_); } -uint8_t next_ledc_channel = 0; +uint8_t next_ledc_channel = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace ledc } // namespace esphome diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 129597cc00..237a8146e6 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -88,8 +88,8 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolved_ = false; ip_addr_t addr; #ifdef ARDUINO_ARCH_ESP32 - err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, this->dns_found_callback, this, - LWIP_DNS_ADDRTYPE_IPV4); + err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, + MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); #endif #ifdef ARDUINO_ARCH_ESP8266 err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index a83618e888..35b904357b 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -25,15 +25,10 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { if (range_end > this->tft_size_) range_end = this->tft_size_; - bool begin_status = false; -#ifdef ARDUINO_ARCH_ESP32 - begin_status = http->begin(this->tft_url_.c_str()); -#endif #ifdef ARDUINO_ARCH_ESP8266 #ifndef CLANG_TIDY http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http->setRedirectLimit(3); - begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif #endif @@ -44,6 +39,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { int tries = 1; int code = 0; + bool begin_status = false; while (tries <= 5) { #ifdef ARDUINO_ARCH_ESP32 begin_status = http->begin(this->tft_url_.c_str()); @@ -156,7 +152,7 @@ void Nextion::upload_tft() { ESP_LOGD(TAG, "connection failed"); #ifdef ARDUINO_ARCH_ESP32 if (psramFound()) - free(this->transfer_buffer_); + free(this->transfer_buffer_); // NOLINT else #endif delete this->transfer_buffer_; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 9a0cf568a9..602c0bdd67 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -45,10 +45,11 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { #ifdef ARDUINO_ARCH_ESP32 bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { + static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); this->pcnt_unit = next_pcnt_unit; - next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); // NOLINT + next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit); @@ -166,9 +167,5 @@ void PulseCounterSensor::update() { } } -#ifdef ARDUINO_ARCH_ESP32 -pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; -#endif - } // namespace pulse_counter } // namespace esphome diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index b3e3f42c01..3203ab81fd 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -69,9 +69,5 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { sensor::Sensor *total_sensor_; }; -#ifdef ARDUINO_ARCH_ESP32 -extern pcnt_unit_t next_pcnt_unit; -#endif - } // namespace pulse_counter } // namespace esphome diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 34f190e45e..9edcd9166c 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace pvvx_mithermometer { -static const char *TAG = "pvvx_mithermometer"; +static const char *const TAG = "pvvx_mithermometer"; void PVVXMiThermometer::dump_config() { ESP_LOGCONFIG(TAG, "PVVX MiThermometer"); @@ -26,7 +26,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic bool success = false; for (auto &service_data : device.get_service_datas()) { auto res = parse_header(service_data); - if (res->is_duplicate) { + if (!res.has_value()) { continue; } if (!(parse_message(service_data.data, *res))) { @@ -46,11 +46,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic success = true; } - if (!success) { - return false; - } - - return true; + return success; } optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) { @@ -64,12 +60,10 @@ optional PVVXMiThermometer::parse_header(const esp32_ble_tracker::S static uint8_t last_frame_count = 0; if (last_frame_count == raw[13]) { - ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast(last_frame_count)); - result.is_duplicate = true; + ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count); return {}; } last_frame_count = raw[13]; - result.is_duplicate = false; return result; } diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 42a40d4200..4132954983 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -14,7 +14,6 @@ struct ParseResult { optional humidity; optional battery_level; optional battery_voltage; - bool is_duplicate; int raw_offset; }; diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index a8b608ec9e..47a85cda57 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -10,7 +10,7 @@ static const uint32_t BIT_TIME_US = 889; static const uint8_t NBITS = 14; void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { - static bool TOGGLE = false; + static bool toggle = false; dst->set_carrier_frequency(36000); uint64_t out_data = 0; @@ -21,7 +21,7 @@ void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { } else { out_data |= 0b11 << 12; } - out_data |= TOGGLE << 11; + out_data |= toggle << 11; out_data |= data.address << 6; out_data |= command; @@ -34,7 +34,7 @@ void RC5Protocol::encode(RemoteTransmitData *dst, const RC5Data &data) { dst->space(BIT_TIME_US); } } - TOGGLE = !TOGGLE; + toggle = !toggle; } optional RC5Protocol::decode(RemoteReceiveData src) { RC5Data out{ diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index a0cdb6ec42..1ad520a099 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -3,7 +3,7 @@ #ifdef USE_SOCKET_IMPL_BSD_SOCKETS -#include +#include namespace esphome { namespace socket { @@ -13,14 +13,14 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { const struct sockaddr_in *addr = reinterpret_cast(&storage); char buf[INET_ADDRSTRLEN]; const char *ret = inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); - if (ret == NULL) + if (ret == nullptr) return {}; return std::string{buf}; } else if (storage.ss_family == AF_INET6) { const struct sockaddr_in6 *addr = reinterpret_cast(&storage); char buf[INET6_ADDRSTRLEN]; const char *ret = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); - if (ret == NULL) + if (ret == nullptr) return {}; return std::string{buf}; } @@ -32,7 +32,7 @@ class BSDSocketImpl : public Socket { BSDSocketImpl(int fd) : Socket(), fd_(fd) {} ~BSDSocketImpl() override { if (!closed_) { - close(); + close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index a5d7fba30a..609b4df456 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -61,7 +61,7 @@ void SPIComponent::setup() { if (spi_bus_num == 0) { this->hw_spi_ = &SPI; } else { - this->hw_spi_ = new SPIClass(VSPI); + this->hw_spi_ = new SPIClass(VSPI); // NOLINT(cppcoreguidelines-owning-memory) } spi_bus_num++; this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index ac8802739b..b44f76a9e6 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -458,26 +458,26 @@ void HOT ST7735::write_display_data_() { } void ST7735::spi_master_write_addr_(uint16_t addr1, uint16_t addr2) { - static uint8_t BYTE[4]; - BYTE[0] = (addr1 >> 8) & 0xFF; - BYTE[1] = addr1 & 0xFF; - BYTE[2] = (addr2 >> 8) & 0xFF; - BYTE[3] = addr2 & 0xFF; + static uint8_t byte[4]; + byte[0] = (addr1 >> 8) & 0xFF; + byte[1] = addr1 & 0xFF; + byte[2] = (addr2 >> 8) & 0xFF; + byte[3] = addr2 & 0xFF; this->dc_pin_->digital_write(true); - this->write_array(BYTE, 4); + this->write_array(byte, 4); } void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) { - static uint8_t BYTE[1024]; + static uint8_t byte[1024]; int index = 0; for (int i = 0; i < size; i++) { - BYTE[index++] = (color >> 8) & 0xFF; - BYTE[index++] = color & 0xFF; + byte[index++] = (color >> 8) & 0xFF; + byte[index++] = color & 0xFF; } this->dc_pin_->digital_write(true); - return write_array(BYTE, size * 2); + return write_array(byte, size * 2); } } // namespace st7735 diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index c84153970d..471ad6664c 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -197,26 +197,26 @@ void ST7789V::write_data_(uint8_t value) { } void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) { - static uint8_t BYTE[4]; - BYTE[0] = (addr1 >> 8) & 0xFF; - BYTE[1] = addr1 & 0xFF; - BYTE[2] = (addr2 >> 8) & 0xFF; - BYTE[3] = addr2 & 0xFF; + static uint8_t byte[4]; + byte[0] = (addr1 >> 8) & 0xFF; + byte[1] = addr1 & 0xFF; + byte[2] = (addr2 >> 8) & 0xFF; + byte[3] = addr2 & 0xFF; this->dc_pin_->digital_write(true); - this->write_array(BYTE, 4); + this->write_array(byte, 4); } void ST7789V::write_color_(uint16_t color, uint16_t size) { - static uint8_t BYTE[1024]; + static uint8_t byte[1024]; int index = 0; for (int i = 0; i < size; i++) { - BYTE[index++] = (color >> 8) & 0xFF; - BYTE[index++] = color & 0xFF; + byte[index++] = (color >> 8) & 0xFF; + byte[index++] = color & 0xFF; } this->dc_pin_->digital_write(true); - return write_array(BYTE, size * 2); + return write_array(byte, size * 2); } int ST7789V::get_height_internal() { diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 041cdaecdf..a79b7b841e 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -126,10 +126,6 @@ class UARTComponent : public Component, public Stream { #endif }; -#ifdef ARDUINO_ARCH_ESP32 -extern uint8_t next_uart_num; -#endif - class UARTDevice : public Stream { public: UARTDevice() = default; diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_esp32.cpp index c672a34c36..db2757780e 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_esp32.cpp @@ -8,7 +8,6 @@ namespace esphome { namespace uart { static const char *const TAG = "uart_esp32"; -uint8_t next_uart_num = 1; static const uint32_t UART_PARITY_EVEN = 0 << 0; static const uint32_t UART_PARITY_ODD = 1 << 0; @@ -80,7 +79,8 @@ void UARTComponent::setup() { #endif this->hw_serial_ = &Serial; } else { - this->hw_serial_ = new HardwareSerial(next_uart_num++); + static uint8_t next_uart_num = 1; + this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) } int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; @@ -99,7 +99,7 @@ void UARTComponent::dump_config() { } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); - ESP_LOGCONFIG(TAG, " Parity: %s", parity_to_str(this->parity_)); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); this->check_logger_conflict_(); } diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32.cpp index b56030db56..38c9e12bec 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32.cpp @@ -25,31 +25,31 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; bool WiFiComponent::wifi_mode_(optional sta, optional ap) { - uint8_t current_mode = WiFi.getMode(); + uint8_t current_mode = WiFiClass::getMode(); bool current_sta = current_mode & 0b01; bool current_ap = current_mode & 0b10; - bool sta_ = sta.value_or(current_sta); - bool ap_ = ap.value_or(current_ap); - if (current_sta == sta_ && current_ap == ap_) + bool enable_sta = sta.value_or(current_sta); + bool enable_ap = ap.value_or(current_ap); + if (current_sta == enable_sta && current_ap == enable_ap) return true; - if (sta_ && !current_sta) { + if (enable_sta && !current_sta) { ESP_LOGV(TAG, "Enabling STA."); - } else if (!sta_ && current_sta) { + } else if (!enable_sta && current_sta) { ESP_LOGV(TAG, "Disabling STA."); } - if (ap_ && !current_ap) { + if (enable_ap && !current_ap) { ESP_LOGV(TAG, "Enabling AP."); - } else if (!ap_ && current_ap) { + } else if (!enable_ap && current_ap) { ESP_LOGV(TAG, "Disabling AP."); } uint8_t mode = 0; - if (sta_) + if (enable_sta) mode |= 0b01; - if (ap_) + if (enable_ap) mode |= 0b10; - bool ret = WiFi.mode(static_cast(mode)); + bool ret = WiFiClass::mode(static_cast(mode)); if (!ret) { ESP_LOGW(TAG, "Setting WiFi mode failed!"); @@ -159,8 +159,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str()); - strcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str()); + strlcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strlcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -176,10 +176,10 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { #endif if (ap.get_bssid().has_value()) { - conf.sta.bssid_set = 1; + conf.sta.bssid_set = true; memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); } else { - conf.sta.bssid_set = 0; + conf.sta.bssid_set = false; } if (ap.get_channel().has_value()) { conf.sta.channel = *ap.get_channel(); @@ -559,7 +559,7 @@ void WiFiComponent::wifi_pre_setup_() { // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { return WiFi.status(); } +wl_status_t WiFiComponent::wifi_sta_status_() { return WiFiClass::status(); } bool WiFiComponent::wifi_scan_start_() { // enable STA if (!this->wifi_mode_(true, {})) @@ -660,7 +660,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str()); + strlcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -671,7 +671,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str()); + strlcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); } #if ESP_IDF_VERSION_MAJOR >= 4 diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 842b44b705..5a0ba92b09 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -581,7 +581,7 @@ wl_status_t WiFiComponent::wifi_sta_status_() { } } bool WiFiComponent::wifi_scan_start_() { - static bool FIRST_SCAN = false; + static bool first_scan = false; // enable STA if (!this->wifi_mode_(true, {})) @@ -595,7 +595,7 @@ bool WiFiComponent::wifi_scan_start_() { config.show_hidden = 1; #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - if (FIRST_SCAN) { + if (first_scan) { config.scan_time.active.min = 100; config.scan_time.active.max = 200; } else { @@ -603,7 +603,7 @@ bool WiFiComponent::wifi_scan_start_() { config.scan_time.active.max = 500; } #endif - FIRST_SCAN = false; + first_scan = false; bool ret = wifi_station_scan(&config, &WiFiComponent::s_wifi_scan_done_callback); if (!ret) { ESP_LOGV(TAG, "wifi_station_scan failed!"); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index f86dc44eeb..3e7e591f9a 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -15,7 +15,7 @@ static const char *const TAG = "xiaomi_ble"; bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { // motion detection, 1 byte, 8-bit unsigned integer if ((value_type == 0x03) && (value_length == 1)) { - result.has_motion = (data[0]) ? true : false; + result.has_motion = data[0]; } // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C else if ((value_type == 0x04) && (value_length == 2)) { @@ -31,7 +31,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) { const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16); result.illuminance = illuminance; - result.is_light = (illuminance == 100) ? true : false; + result.is_light = illuminance == 100; if (value_type == 0x0F) result.has_motion = true; } @@ -62,7 +62,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l } // on/off state, 1 byte, 8-bit unsigned integer else if ((value_type == 0x12) && (value_length == 1)) { - result.is_active = (data[0]) ? true : false; + result.is_active = data[0]; } // mosquito tablet, 1 byte, 8-bit unsigned integer, 1 % else if ((value_type == 0x13) && (value_length == 1)) { @@ -72,7 +72,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l else if ((value_type == 0x17) && (value_length == 4)) { const uint32_t idle_time = encode_uint32(data[3], data[2], data[1], data[0]); result.idle_time = idle_time / 60.0f; - result.has_motion = (idle_time) ? false : true; + result.has_motion = !idle_time; } else { return false; } @@ -81,7 +81,7 @@ bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_l } bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result) { - result.has_encryption = (message[0] & 0x08) ? true : false; // update encryption status + result.has_encryption = message[0] & 0x08; // update encryption status if (result.has_encryption) { ESP_LOGVV(TAG, "parse_xiaomi_message(): payload is encrypted, stop reading message."); return false; @@ -136,9 +136,9 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service } auto raw = service_data.data; - result.has_data = (raw[0] & 0x40) ? true : false; - result.has_capability = (raw[0] & 0x20) ? true : false; - result.has_encryption = (raw[0] & 0x08) ? true : false; + result.has_data = raw[0] & 0x40; + result.has_capability = raw[0] & 0x20; + result.has_encryption = raw[0] & 0x08; if (!result.has_data) { ESP_LOGVV(TAG, "parse_xiaomi_header(): service data has no DATA flag."); diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index c6e7a3f962..cfbe986fff 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -52,11 +52,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGD1::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGD1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 2ba2ac4c0a..8f42eaecaf 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -52,11 +52,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 86192fb028..d0a91d23f0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -52,11 +52,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGG1::set_bindkey(const std::string &bindkey) { @@ -67,7 +63,7 @@ void XiaomiCGG1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 2ed1024076..75ed947c25 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace xiaomi_cgpr1 { -static const char *TAG = "xiaomi_cgpr1"; +static const char *const TAG = "xiaomi_cgpr1"; void XiaomiCGPR1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGPR1"); @@ -54,11 +54,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { @@ -69,7 +65,7 @@ void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 5f7d67b85a..f626daad88 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -53,11 +53,7 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_gcls002 diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 103201d511..3f2cbf963a 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -56,11 +56,7 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_hhccjcy01 diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index efc83cb6cc..92b7171004 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -47,11 +47,7 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_hhccpot002 diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index c88a3c3b61..646796525e 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -53,11 +53,7 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_jqjcy01ym diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index 6b8ecdeaff..1ecf0606a2 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -50,11 +50,7 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_lywsd02 diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index f0a2cee8d4..3eb0b68318 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -56,11 +56,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { @@ -71,7 +67,7 @@ void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 39bcd9df03..f16fce296b 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -50,11 +50,7 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_lywsdcgq diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index e93a4b91ae..fdbb799ea6 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -56,11 +56,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { @@ -71,7 +67,7 @@ void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp index cc63e46573..34c5a975c7 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp @@ -71,8 +71,8 @@ bool XiaomiMiscale2::parse_message(const std::vector &message, ParseRes return false; } - bool is_Stabilized = ((data[1] & (1 << 5)) != 0) ? true : false; - bool loadRemoved = ((data[1] & (1 << 7)) != 0) ? true : false; + bool is_stabilized = ((data[1] & (1 << 5)) != 0); + bool load_removed = ((data[1] & (1 << 7)) != 0); // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); @@ -85,11 +85,7 @@ bool XiaomiMiscale2::parse_message(const std::vector &message, ParseRes const int16_t impedance = uint16_t(data[9]) | (uint16_t(data[10]) << 8); result.impedance = impedance; - if (!is_Stabilized || loadRemoved || impedance == 0 || impedance >= 3000) { - return false; - } - - return true; + return is_stabilized && !load_removed && impedance != 0 && impedance < 3000; } bool XiaomiMiscale2::report_results(const optional &result, const std::string &address) { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 739543ddb6..df1cede068 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -57,11 +57,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { @@ -72,7 +68,7 @@ void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { char temp[3] = {0}; for (int i = 0; i < 16; i++) { strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, NULL, 16); + bindkey_[i] = std::strtoul(temp, nullptr, 16); } } diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index c1eff2e066..53429c1806 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -46,11 +46,7 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_mue4094rt diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index d6e2ebe5b2..7529bb7431 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -51,11 +51,7 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { success = true; } - if (!success) { - return false; - } - - return true; + return success; } } // namespace xiaomi_wx08zm diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index fac17a8271..c0f68d80d6 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -111,11 +111,11 @@ void Application::loop() { } void ICACHE_RAM_ATTR HOT Application::feed_wdt() { - static uint32_t LAST_FEED = 0; + static uint32_t last_feed = 0; uint32_t now = millis(); - if (now - LAST_FEED > 3) { + if (now - last_feed > 3) { this->feed_wdt_arch_(); - LAST_FEED = now; + last_feed = now; #ifdef USE_STATUS_LED if (status_led::global_status_led != nullptr) { status_led::global_status_led->call(); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 3cca6445b5..45bb3b82a5 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -11,7 +11,6 @@ #define ESPHOME_PROJECT_VERSION "v2" // Feature flags -#define USE_ADC_SENSOR_VCC #define USE_API #define USE_BINARY_SENSOR #define USE_CAPTIVE_PORTAL @@ -46,12 +45,12 @@ #define USE_ESP32_CAMERA #define USE_ETHERNET #define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS #endif #ifdef ARDUINO_ARCH_ESP8266 +#define USE_ADC_SENSOR_VCC #define USE_SOCKET_IMPL_LWIP_TCP -#else -#define USE_SOCKET_IMPL_BSD_SOCKETS #endif #define USE_API_PLAINTEXT diff --git a/platformio.ini b/platformio.ini index f4dea3fcb9..a35e0c10fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,7 +69,6 @@ lib_deps = Update build_flags = ${common.build_flags} - -DUSE_ETHERNET src_filter = ${common.src_filter} - diff --git a/script/clang-tidy b/script/clang-tidy index c2f47bad11..a63ebc674a 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -16,7 +16,7 @@ import click import pexpect sys.path.append(os.path.dirname(__file__)) -from helpers import shlex_quote, get_output, \ +from helpers import shlex_quote, get_output, filter_grep, \ build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata @@ -27,13 +27,15 @@ def clang_options(idedata): # disable built-in include directories from the host '-nostdinc', '-nostdinc++', + # pretend we're an Xtensa compiler, which gates some features in the headers + '-D__XTENSA__', # allow to condition code on the presence of clang-tidy '-DCLANG_TIDY' ] # copy compiler flags, except those clang doesn't understand. cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-mlongcalls', '-mtext-section-literals')) + if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', '-mlongcalls', '-mtext-section-literals')) # defines cmd.extend(f'-D{define}' for define in idedata['defines']) @@ -97,6 +99,8 @@ def main(): parser.add_argument('-j', '--jobs', type=int, default=multiprocessing.cpu_count(), help='number of tidy instances to be run in parallel.') + parser.add_argument('-e', '--environment', default='esp8266-tidy', + help='the PlatformIO environment to run against (esp8266-tidy or esp32-tidy)') parser.add_argument('files', nargs='*', default=[], help='files to be processed (regex on path)') parser.add_argument('--fix', action='store_true', help='apply fix-its') @@ -104,6 +108,7 @@ def main(): help='run clang-tidy in quiet mode') parser.add_argument('-c', '--changed', action='store_true', help='only run on changed files') + parser.add_argument('-g', '--grep', help='only run on files containing value') parser.add_argument('--split-num', type=int, help='split the files into X jobs.', default=None) parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', @@ -126,7 +131,7 @@ def main(): """) return 1 - idedata = load_idedata("esp8266-tidy") + idedata = load_idedata(args.environment) options = clang_options(idedata) files = [] @@ -141,6 +146,9 @@ def main(): if args.changed: files = filter_changed(files) + if args.grep: + files = filter_grep(files, args.grep) + files.sort() if args.split_num: diff --git a/script/helpers.py b/script/helpers.py index 7200078822..d32e0eafbe 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -92,6 +92,16 @@ def filter_changed(files): return files +def filter_grep(files, value): + matched = [] + for file in files: + with open(file, "r") as handle: + contents = handle.read() + if value in contents: + matched.append(file) + return matched + + def git_ls_files(patterns=None): command = ["git", "ls-files", "-s"] if patterns is not None: From ed7983af41e16e8af56b39913bd62918dfb6bc9f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 18:52:53 +0200 Subject: [PATCH 221/285] Fix API socket issues (#2288) * Fix API socket issues * Fix compile error against beta * Format --- esphome/components/api/api_connection.cpp | 74 ++++++++++++------- esphome/components/api/api_connection.h | 16 +--- esphome/components/api/api_frame_helper.cpp | 70 +++++++++++++----- esphome/components/api/api_frame_helper.h | 3 +- esphome/components/api/api_server.cpp | 2 +- .../components/socket/lwip_raw_tcp_impl.cpp | 54 +++++++++----- 6 files changed, 142 insertions(+), 77 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a530554155..58a5662055 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -36,19 +36,14 @@ void APIConnection::start() { APIError err = helper_->init(); if (err != APIError::OK) { - ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno); - remove_ = true; + on_fatal_error(); + ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } client_info_ = helper_->getpeername(); helper_->set_log_info(client_info_); } -void APIConnection::force_disconnect_client() { - this->helper_->close(); - this->remove_ = true; -} - void APIConnection::loop() { if (this->remove_) return; @@ -57,9 +52,11 @@ void APIConnection::loop() { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); + ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str()); return; } if (this->next_close_) { + // requested a disconnect this->helper_->close(); this->remove_ = true; return; @@ -68,7 +65,7 @@ void APIConnection::loop() { APIError err = helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); return; } ReadPacketBuffer buffer; @@ -77,7 +74,11 @@ void APIConnection::loop() { // pass } else if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err); + if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return; } else { this->last_traffic_ = millis(); @@ -95,8 +96,8 @@ void APIConnection::loop() { if (this->sent_ping_) { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > (keepalive * 5) / 2) { - this->force_disconnect_client(); - ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); + on_fatal_error(); + ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); } } else if (now - this->last_traffic_ > keepalive) { this->sent_ping_ = true; @@ -124,12 +125,40 @@ void APIConnection::loop() { } } #endif + + if (state_subs_at_ != -1) { + const auto &subs = this->parent_->get_state_subs(); + if (state_subs_at_ >= subs.size()) { + state_subs_at_ = -1; + } else { + auto &it = subs[state_subs_at_]; + SubscribeHomeAssistantStateResponse resp; + resp.entity_id = it.entity_id; + resp.attribute = it.attribute.value(); + if (this->send_subscribe_home_assistant_state_response(resp)) { + state_subs_at_++; + } + } + } } std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { return App.get_name() + component_type + nameable->get_object_id(); } +DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { + // remote initiated disconnect_client + // don't close yet, we still need to send the disconnect response + // close will happen on next loop + ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str()); + this->next_close_ = true; + DisconnectResponse resp; + return resp; +} +void APIConnection::on_disconnect_response(const DisconnectResponse &value) { + // pass +} + #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) { if (!this->state_subscription_) @@ -703,7 +732,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { // bool invalid_password = 1; resp.invalid_password = !correct; if (correct) { - ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; #ifdef USE_HOMEASSISTANT_TIME @@ -749,15 +778,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { } } void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) { - for (auto &it : this->parent_->get_state_subs()) { - SubscribeHomeAssistantStateResponse resp; - resp.entity_id = it.entity_id; - resp.attribute = it.attribute.value(); - if (!this->send_subscribe_home_assistant_state_response(resp)) { - this->on_fatal_error(); - return; - } - } + state_subs_at_ = 0; } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) @@ -770,7 +791,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) return false; if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno); + if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { + ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else { + ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + } return false; } this->last_traffic_ = millis(); @@ -778,14 +803,13 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } void APIConnection::on_unauthenticated_access() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str()); } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str()); } void APIConnection::on_fatal_error() { - ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str()); this->helper_->close(); this->remove_ = true; } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index a1788bbede..a1f1769a19 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -16,7 +16,6 @@ class APIConnection : public APIServerConnection { virtual ~APIConnection() = default; void start(); - void force_disconnect_client(); void loop(); bool send_list_info_done() { @@ -88,10 +87,7 @@ class APIConnection : public APIServerConnection { } #endif - void on_disconnect_response(const DisconnectResponse &value) override { - this->helper_->close(); - this->remove_ = true; - } + void on_disconnect_response(const DisconnectResponse &value) override; void on_ping_response(const PingResponse &value) override { // we initiated ping this->sent_ping_ = false; @@ -102,14 +98,7 @@ class APIConnection : public APIServerConnection { #endif HelloResponse hello(const HelloRequest &msg) override; ConnectResponse connect(const ConnectRequest &msg) override; - DisconnectResponse disconnect(const DisconnectRequest &msg) override { - // remote initiated disconnect_client - // don't close yet, we still need to send the disconnect response - // close will happen on next loop - this->next_close_ = true; - DisconnectResponse resp; - return resp; - } + DisconnectResponse disconnect(const DisconnectRequest &msg) override; PingResponse ping(const PingRequest &msg) override { return {}; } DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } @@ -177,6 +166,7 @@ class APIConnection : public APIServerConnection { APIServer *parent_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; + int state_subs_at_ = -1; }; } // namespace api diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 520a5c2caf..c064c7278f 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -17,6 +17,54 @@ bool is_would_block(ssize_t ret) { return ret == 0; } +const char *api_error_to_str(APIError err) { + // not using switch to ensure compiler doesn't try to build a big table out of it + if (err == APIError::OK) { + return "OK"; + } else if (err == APIError::WOULD_BLOCK) { + return "WOULD_BLOCK"; + } else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) { + return "BAD_HANDSHAKE_PACKET_LEN"; + } else if (err == APIError::BAD_INDICATOR) { + return "BAD_INDICATOR"; + } else if (err == APIError::BAD_DATA_PACKET) { + return "BAD_DATA_PACKET"; + } else if (err == APIError::TCP_NODELAY_FAILED) { + return "TCP_NODELAY_FAILED"; + } else if (err == APIError::TCP_NONBLOCKING_FAILED) { + return "TCP_NONBLOCKING_FAILED"; + } else if (err == APIError::CLOSE_FAILED) { + return "CLOSE_FAILED"; + } else if (err == APIError::SHUTDOWN_FAILED) { + return "SHUTDOWN_FAILED"; + } else if (err == APIError::BAD_STATE) { + return "BAD_STATE"; + } else if (err == APIError::BAD_ARG) { + return "BAD_ARG"; + } else if (err == APIError::SOCKET_READ_FAILED) { + return "SOCKET_READ_FAILED"; + } else if (err == APIError::SOCKET_WRITE_FAILED) { + return "SOCKET_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) { + return "HANDSHAKESTATE_READ_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) { + return "HANDSHAKESTATE_WRITE_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) { + return "HANDSHAKESTATE_BAD_STATE"; + } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) { + return "CIPHERSTATE_DECRYPT_FAILED"; + } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) { + return "CIPHERSTATE_ENCRYPT_FAILED"; + } else if (err == APIError::OUT_OF_MEMORY) { + return "OUT_OF_MEMORY"; + } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) { + return "HANDSHAKESTATE_SETUP_FAILED"; + } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { + return "HANDSHAKESTATE_SPLIT_FAILED"; + } + return "UNKNOWN"; +} + #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) #ifdef USE_API_NOISE @@ -808,14 +856,12 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf while (state_ != State::CLOSED && !tx_buf_.empty()) { ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size()); - if (sent == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - break; + if (is_would_block(sent)) { + break; + } else if (sent == -1) { state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent == 0) { - break; } // TODO: inefficient if multiple packets in txbuf // replace with deque of buffers @@ -869,20 +915,6 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { // fully sent return APIError::OK; } -APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - - uint8_t header[3]; - header[0] = 0x01; // indicator - header[1] = (uint8_t)(len >> 8); - header[2] = (uint8_t) len; - - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; -} APIError APIPlaintextFrameHelper::close() { state_ = State::CLOSED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7189bc4b4b..a8974cd25f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,8 @@ enum class APIError : int { HANDSHAKESTATE_SPLIT_FAILED = 1020, }; +const char *api_error_to_str(APIError err); + class APIFrameHelper { public: virtual APIError init() = 0; @@ -150,7 +152,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_frame_(const uint8_t *data, size_t len); APIError write_raw_(const uint8_t *data, size_t len); std::unique_ptr socket_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1ef567ab57..d84a35747c 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -105,7 +105,7 @@ void APIServer::loop() { [](const std::unique_ptr &conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { - ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); + ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str()); } // resize vector this->clients_.erase(new_end, this->clients_.end()); diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 29e1b23823..2147e36632 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -109,14 +109,17 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { + LWIP_LOG(" -> err ERR_USE"); errno = EADDRINUSE; return -1; } if (err == ERR_VAL) { + LWIP_LOG(" -> err ERR_VAL"); errno = EINVAL; return -1; } if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = EIO; return -1; } @@ -124,12 +127,13 @@ class LWIPRawImpl : public Socket { } int close() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } LWIP_LOG("tcp_close(%p)", pcb_); err_t err = tcp_close(pcb_); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); tcp_abort(pcb_); pcb_ = nullptr; errno = err == ERR_MEM ? ENOMEM : EIO; @@ -140,7 +144,7 @@ class LWIPRawImpl : public Socket { } int shutdown(int how) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } bool shut_rx = false, shut_tx = false; @@ -157,6 +161,7 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_shutdown(%p shut_rx=%d shut_tx=%d)", pcb_, shut_rx ? 1 : 0, shut_tx ? 1 : 0); err_t err = tcp_shutdown(pcb_, shut_rx, shut_tx); if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); errno = err == ERR_MEM ? ENOMEM : EIO; return -1; } @@ -165,7 +170,7 @@ class LWIPRawImpl : public Socket { int getpeername(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -185,7 +190,7 @@ class LWIPRawImpl : public Socket { } std::string getpeername() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -196,7 +201,7 @@ class LWIPRawImpl : public Socket { } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (name == nullptr || addrlen == nullptr) { @@ -216,7 +221,7 @@ class LWIPRawImpl : public Socket { } std::string getsockname() override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return ""; } char buffer[24]; @@ -227,7 +232,7 @@ class LWIPRawImpl : public Socket { } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (optlen == nullptr || optval == nullptr) { @@ -261,7 +266,7 @@ class LWIPRawImpl : public Socket { } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (level == SOL_SOCKET && optname == SO_REUSEADDR) { @@ -314,7 +319,7 @@ class LWIPRawImpl : public Socket { } ssize_t read(void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (rx_closed_ && rx_buf_ == nullptr) { @@ -368,7 +373,7 @@ class LWIPRawImpl : public Socket { } ssize_t write(const void *buf, size_t len) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (len == 0) @@ -386,24 +391,37 @@ class LWIPRawImpl : public Socket { LWIP_LOG("tcp_write(%p buf=%p %u)", pcb_, buf, to_send); err_t err = tcp_write(pcb_, buf, to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_MEM) { + LWIP_LOG(" -> err ERR_MEM"); errno = EWOULDBLOCK; return -1; } if (err != ERR_OK) { - errno = EIO; + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; return -1; } - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err != ERR_OK) { - errno = EIO; - return -1; + if (tcp_nagle_disabled(pcb_)) { + LWIP_LOG("tcp_output(%p)", pcb_); + err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return to_send; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } } return to_send; } int setblocking(bool blocking) override { if (pcb_ == nullptr) { - errno = EBADF; + errno = ECONNRESET; return -1; } if (blocking) { @@ -466,7 +484,7 @@ class LWIPRawImpl : public Socket { static void s_err_fn(void *arg, err_t err) { LWIPRawImpl *arg_this = reinterpret_cast(arg); - return arg_this->err_fn(err); + arg_this->err_fn(err); } static err_t s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) { From 924df1e7de1a6f11d5ab75ed1e94ed3dcd739134 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:55:04 +0200 Subject: [PATCH 222/285] Run clang-tidy against Arduino 3 (#2146) * Add macros header with more usable Arduino version defines * Change Arduino version checking to use our version defines * Add missing ESP8266 check * Rename Arduino version macro to ARDUINO_VERSION_CODE * Upgrade clang-tidy to use Arduino 3 * Fix clang-tidy warnings * Upgrade NeoPixelBus to upstream 2.6.7 * Use Arduino-version-appropriate API to set redirect flags * Remove now unnecessary CLANG_TIDY ifdefs * Add preprocessor hackery to avoid including pgmspace.h * Bump base image to 4.1.1 and update lint * Fix nfctag * Fix make_unique ambiguous * Fix ignore name * Fix ambiguous v2 * Remove unused begin * Cast time_t to prevent issues on platforms where time_t is 32bit Co-authored-by: Otto winter --- .clang-tidy | 5 +++- .github/workflows/ci.yml | 2 +- .github/workflows/docker-lint-build.yml | 4 +-- docker/Dockerfile.dev | 2 +- docker/build.py | 2 +- esphome/components/adc/adc_sensor.cpp | 2 +- esphome/components/debug/debug_component.cpp | 11 ++++---- .../deep_sleep/deep_sleep_component.cpp | 2 +- .../components/http_request/http_request.cpp | 11 +++++--- esphome/components/light/light_color_values.h | 1 + esphome/components/neopixelbus/light.py | 2 +- esphome/components/nextion/nextion_upload.cpp | 25 +++++++++++++------ esphome/components/nfc/ndef_message.cpp | 4 +-- esphome/components/nfc/nfc_tag.h | 4 +-- esphome/components/pn532/pn532.cpp | 8 +++--- .../components/pn532/pn532_mifare_classic.cpp | 8 +++--- .../pn532/pn532_mifare_ultralight.cpp | 10 ++++---- esphome/components/sgp40/sgp40.cpp | 1 + .../components/shutdown/shutdown_switch.cpp | 2 +- esphome/components/spi/spi.cpp | 6 ++--- esphome/components/time/automation.cpp | 4 +-- esphome/components/uart/uart_esp8266.cpp | 6 ++--- .../web_server_base/web_server_base.cpp | 1 + esphome/core/application.cpp | 4 +-- esphome/core/application_esp8266.cpp | 4 ++- esphome/core/helpers.h | 7 ------ esphome/core/util.cpp | 4 +-- esphome/core/util.h | 2 +- platformio.ini | 5 ++-- script/clang-tidy | 15 +++++++++++ 30 files changed, 98 insertions(+), 66 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 8451a2bdd4..890eb18608 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -63,11 +63,15 @@ Checks: >- -misc-no-recursion, -misc-unused-parameters, -modernize-avoid-c-arrays, + -modernize-avoid-bind, + -modernize-concat-nested-namespaces, -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-trailing-return-type, + -modernize-make-unique, + -modernize-use-nodiscard, -mpi-*, -objc-*, -readability-braces-around-statements, @@ -86,7 +90,6 @@ Checks: >- -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, - -warnings-as-errors WarningsAsErrors: '*' AnalyzeTemporaryDtors: false FormatStyle: google diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb93c36d19..b00e1b3835 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: ghcr.io/esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.2 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index 32aec87cdd..8350d4c719 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -29,7 +29,7 @@ jobs: python-version: '3.9' - name: Set TAG run: | - echo "TAG=1.1" >> $GITHUB_ENV + echo "TAG=1.2" >> $GITHUB_ENV - name: Run build run: | @@ -74,7 +74,7 @@ jobs: python-version: '3.9' - name: Set TAG run: | - echo "TAG=1.1" >> $GITHUB_ENV + echo "TAG=1.2" >> $GITHUB_ENV - name: Enable experimental manifest support run: | mkdir -p ~/.docker diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 27eaa18da9..c646ce989b 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1 +1 @@ -FROM esphome/esphome-lint:1.1 +FROM esphome/esphome-lint:1.2 diff --git a/docker/build.py b/docker/build.py index 54a279f845..039d7dc15b 100755 --- a/docker/build.py +++ b/docker/build.py @@ -24,7 +24,7 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "3.6.0" +BASE_VERSION = "4.1.1" parser = argparse.ArgumentParser() diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 78f12a6b9e..4106a0c49b 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -148,7 +148,7 @@ float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; + return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) #else return analogRead(this->pin_) / 1024.0f; // NOLINT #endif diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 668792c7b1..11590a0978 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -21,11 +21,11 @@ void DebugComponent::dump_config() { #endif ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); - this->free_heap_ = ESP.getFreeHeap(); + this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); const char *flash_mode; - switch (ESP.getFlashChipMode()) { + switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: flash_mode = "QIO"; break; @@ -49,6 +49,7 @@ void DebugComponent::dump_config() { default: flash_mode = "UNKNOWN"; } + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, ESP.getFlashChipSpeed() / 1000000, flash_mode); @@ -87,7 +88,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - std::string mac = uint64_to_string(ESP.getEfuseMac()); + std::string mac = uint64_to_string(ESP.getEfuseMac()); // NOLINT(readability-static-accessed-through-instance) ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); const char *reset_reason; @@ -186,7 +187,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(ARDUINO_ARCH_ESP8266) && !defined(CLANG_TIDY) ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); @@ -198,7 +199,7 @@ void DebugComponent::dump_config() { #endif } void DebugComponent::loop() { - uint32_t new_free_heap = ESP.getFreeHeap(); + uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index de5672759a..00f660f41a 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -84,7 +84,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { #endif #ifdef ARDUINO_ARCH_ESP8266 - ESP.deepSleep(*this->sleep_duration_); + ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif } float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; } diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index bf5b24c4f2..0d4e425147 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,4 +1,5 @@ #include "http_request.h" +#include "esphome/core/macros.h" #include "esphome/core/log.h" namespace esphome { @@ -31,11 +32,15 @@ void HttpRequestComponent::send(const std::vector begin_status = this->client_.begin(url); #endif #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) + this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) this->client_.setFollowRedirects(true); - this->client_.setRedirectLimit(3); - begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + this->client_.setRedirectLimit(3); +#endif + begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif if (!begin_status) { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index dd74c396c6..ffbe378ee3 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "color_mode.h" +#include namespace esphome { namespace light { diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 59d784a614..575f69b731 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -205,4 +205,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("NeoPixelBus-esphome", "2.6.2") + cg.add_library("NeoPixelBus", "2.6.7") diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 35b904357b..f864a397cc 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,6 +1,7 @@ #include "nextion.h" #include "esphome/core/application.h" +#include "esphome/core/macros.h" #include "esphome/core/util.h" #include "esphome/core/log.h" @@ -26,8 +27,12 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { range_end = this->tft_size_; #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http->setFollowRedirects(true); +#endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setRedirectLimit(3); #endif #endif @@ -44,10 +49,8 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { #ifdef ARDUINO_ARCH_ESP32 begin_status = http->begin(this->tft_url_.c_str()); #endif -#ifndef CLANG_TIDY #ifdef ARDUINO_ARCH_ESP8266 begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif #endif ++tries; @@ -140,11 +143,15 @@ void Nextion::upload_tft() { begin_status = http.begin(this->tft_url_.c_str()); #endif #ifdef ARDUINO_ARCH_ESP8266 -#ifndef CLANG_TIDY +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setRedirectLimit(3); - begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http.setFollowRedirects(true); #endif +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) + http.setRedirectLimit(3); +#endif + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif if (!begin_status) { @@ -256,6 +263,7 @@ void Nextion::upload_tft() { } } #else + // NOLINTNEXTLINE(readability-static-accessed-through-instance) uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; #endif @@ -270,6 +278,7 @@ void Nextion::upload_tft() { } } else { #endif + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) if (this->transfer_buffer_ == nullptr) { // Try a smaller size @@ -288,6 +297,7 @@ void Nextion::upload_tft() { this->transfer_buffer_size_ = chunk_size; } + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); @@ -299,6 +309,7 @@ void Nextion::upload_tft() { this->upload_end_(); } App.feed_wdt(); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); } ESP_LOGD(TAG, "Successfully updated Nextion!"); @@ -311,7 +322,7 @@ void Nextion::upload_end_() { this->soft_reset(); delay(1500); // NOLINT ESP_LOGD(TAG, "Restarting esphome"); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) } #ifdef ARDUINO_ARCH_ESP8266 diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index b1554f41ae..147392940c 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -83,11 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); + return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "T", payload)}); } bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); + return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "U", uri)}); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ab6ca650e4..2c8b0a5f21 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -31,13 +31,13 @@ class NfcTag { NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = make_unique(ndef_data); + this->ndef_message_ = std::unique_ptr(new NdefMessage(ndef_data)); }; NfcTag(const NfcTag &rhs) { uid_ = rhs.uid_; tag_type_ = rhs.tag_type_; if (rhs.ndef_message_ != nullptr) - ndef_message_ = make_unique(*rhs.ndef_message_); + ndef_message_ = std::unique_ptr(new NdefMessage(*rhs.ndef_message_)); } std::vector &get_uid() { return this->uid_; }; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 8fda19cba3..c28ce8d503 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -104,7 +104,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = make_unique(this->current_uid_); + auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +117,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = make_unique(this->current_uid_); + auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -281,9 +281,9 @@ std::unique_ptr PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return make_unique(uid); + return std::unique_ptr{new nfc::NfcTag(uid)}; } else { - return make_unique(uid); + return std::unique_ptr{new nfc::NfcTag(uid)}; } } diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index fa99e5cfab..f4bd11d49f 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -15,15 +15,15 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return make_unique(uid, nfc::ERROR); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::ERROR)}; } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return make_unique(uid, nfc::MIFARE_CLASSIC); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return make_unique(uid, nfc::MIFARE_CLASSIC); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; } uint32_t index = 0; @@ -51,7 +51,7 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector(uid, nfc::MIFARE_CLASSIC, buffer); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer)}; } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 6f118419ba..24e97ab95c 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -9,25 +9,25 @@ static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +38,7 @@ std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector(uid, nfc::NFC_FORUM_TYPE_2, data); + return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data)}; } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index a911c107b9..1a1909eeb0 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -1,5 +1,6 @@ #include "esphome/core/log.h" #include "sgp40.h" +#include namespace esphome { namespace sgp40 { diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 3cc8ba9e1b..87b755cab6 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -18,7 +18,7 @@ void ShutdownSwitch::write_state(bool state) { App.run_safe_shutdown_hooks(); #ifdef ARDUINO_ARCH_ESP8266 - ESP.deepSleep(0); + ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) #endif #ifdef ARDUINO_ARCH_ESP32 esp_deep_sleep_start(); diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 609b4df456..3180447711 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -95,12 +95,12 @@ void SPIComponent::debug_rx(uint8_t value) { void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } void SPIComponent::cycle_clock_(bool value) { - uint32_t start = ESP.getCycleCount(); - while (start - ESP.getCycleCount() < this->wait_cycle_) + uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) ; this->clk_->digital_write(value); start += this->wait_cycle_; - while (start - ESP.getCycleCount() < this->wait_cycle_) + while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) ; } diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 84cc76a762..f133ab021c 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -43,9 +43,9 @@ void CronTrigger::loop() { this->last_check_ = time; if (!time.fields_in_range()) { ESP_LOGW(TAG, "Time is out of range!"); - ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u DayOfWeek=%u DayOfMonth=%u DayOfYear=%u Month=%u time=%ld", + ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u DayOfWeek=%u DayOfMonth=%u DayOfYear=%u Month=%u time=%" PRId64, time.second, time.minute, time.hour, time.day_of_week, time.day_of_month, time.day_of_year, time.month, - time.timestamp); + (int64_t) time.timestamp); } if (this->matches(time)) diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_esp8266.cpp index 6d207fde6c..a74f2601e4 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_esp8266.cpp @@ -233,7 +233,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra } void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; - const uint32_t start = ESP.getCycleCount(); + const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) uint8_t rec = 0; // Manually unroll the loop for (int i = 0; i < arg->data_bits_; i++) @@ -273,7 +273,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { { InterruptLock lock; uint32_t wait = this->bit_time_; - const uint32_t start = ESP.getCycleCount(); + const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) // Start bit this->write_bit_(false, &wait, start); for (int i = 0; i < this->data_bits_; i++) { @@ -291,7 +291,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { } } void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { - while (ESP.getCycleCount() - start < *wait) + while (ESP.getCycleCount() - start < *wait) // NOLINT(readability-static-accessed-through-instance) ; *wait += this->bit_time_; } diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 9f04c3b7bf..6351941aee 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -29,6 +29,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin this->ota_read_length_ = 0; #ifdef ARDUINO_ARCH_ESP8266 Update.runAsync(true); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c0f68d80d6..5ab1d973f4 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -127,7 +127,7 @@ void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot..."); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution while (true) { yield(); @@ -139,7 +139,7 @@ void Application::safe_reboot() { comp->on_safe_shutdown(); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution while (true) { yield(); diff --git a/esphome/core/application_esp8266.cpp b/esphome/core/application_esp8266.cpp index 95139ca112..ee32417634 100644 --- a/esphome/core/application_esp8266.cpp +++ b/esphome/core/application_esp8266.cpp @@ -6,7 +6,9 @@ namespace esphome { static const char *const TAG = "app_esp8266"; -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { ESP.wdtFeed(); } +void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { + ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) +} } // namespace esphome #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 044de6c8b0..86c70088d4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -12,13 +12,6 @@ #include "esphome/core/optional.h" -#ifdef CLANG_TIDY -#undef ICACHE_RAM_ATTR -#define ICACHE_RAM_ATTR -#undef ICACHE_RODATA_ATTR -#define ICACHE_RODATA_ATTR -#endif - #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) diff --git a/esphome/core/util.cpp b/esphome/core/util.cpp index 67e575e1c5..e0132e2e4a 100644 --- a/esphome/core/util.cpp +++ b/esphome/core/util.cpp @@ -75,12 +75,12 @@ static const uint8_t WEBSERVER_PORT = 80; #ifdef USE_MDNS #ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(IPAddress address, int interface) { +void network_setup_mdns(const IPAddress &address, int interface) { // Latest arduino framework breaks mDNS for AP interface // see https://github.com/esp8266/Arduino/issues/6114 if (interface == 1) return; - MDNS.begin(App.get_name().c_str(), std::move(address)); + MDNS.begin(App.get_name().c_str(), address); mdns_setup = true; #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/esphome/core/util.h b/esphome/core/util.h index 2d58eff893..764c6aaf03 100644 --- a/esphome/core/util.h +++ b/esphome/core/util.h @@ -21,7 +21,7 @@ bool remote_is_connected(); /// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode) #ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(IPAddress address, int interface); +void network_setup_mdns(const IPAddress &address, int interface); #endif #ifdef ARDUINO_ARCH_ESP32 void network_setup_mdns(); diff --git a/platformio.ini b/platformio.ini index a35e0c10fe..3b1241f282 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ lib_deps = ArduinoJson-esphomelib@5.13.3 esphome/ESPAsyncWebServer-esphome@1.3.0 FastLED@3.3.2 - NeoPixelBus-esphome@2.6.2 + NeoPixelBus@2.6.7 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 @@ -47,8 +47,7 @@ src_filter = +<.temp/all-include.cpp> [common:esp8266] -; use Arduino framework v2.4.2 for clang-tidy (latest 2.5.2 breaks static code analysis, see #760) -platform = platformio/espressif8266@1.8.0 +platform = platformio/espressif8266@3.1.0 framework = arduino board = nodemcuv2 lib_deps = diff --git a/script/clang-tidy b/script/clang-tidy index a63ebc674a..463959fa5d 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -27,6 +27,21 @@ def clang_options(idedata): # disable built-in include directories from the host '-nostdinc', '-nostdinc++', + # replace pgmspace.h, as it uses GNU extensions clang doesn't support + # https://github.com/earlephilhower/newlib-xtensa/pull/18 + '-D_PGMSPACE_H_', + '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', + '-DPROGMEM=', + '-DPGM_P=const char *', + '-DPSTR(s)=(s)', + # this next one is also needed with upstream pgmspace.h + # suppress warning about identifier naming in expansion of this macro + '-DPSTRN(s, n)=(s)', + # suppress warning about attribute cannot be applied to type + # https://github.com/esp8266/Arduino/pull/8258 + '-Ddeprecated(x)=', # pretend we're an Xtensa compiler, which gates some features in the headers '-D__XTENSA__', # allow to condition code on the presence of clang-tidy From e6b0a0ca2b9fbf329d8233c322257078d2f5a7b6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Sep 2021 18:58:49 +0200 Subject: [PATCH 223/285] Clean-up sensor integration (#2275) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 15 +-- esphome/components/sensor/filter.cpp | 28 ----- esphome/components/sensor/filter.h | 19 --- esphome/components/sensor/sensor.cpp | 106 +++++++--------- esphome/components/sensor/sensor.h | 140 ++++++++-------------- 7 files changed, 102 insertions(+), 210 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 58a5662055..31a530c04d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -422,7 +422,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); - msg.state_class = static_cast(sensor->state_class); + msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); return this->send_list_entities_sensor_response(msg); diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 344aaf26f8..9a35674124 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -11,7 +11,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { public: void update() override { float val = random_float(); - bool increasing = this->state_class == sensor::STATE_CLASS_TOTAL_INCREASING; + bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { float base = isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index d440e30fc4..e921056167 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -31,16 +31,9 @@ void MQTTSensorComponent::dump_config() { std::string MQTTSensorComponent::component_type() const { return "sensor"; } uint32_t MQTTSensorComponent::get_expire_after() const { - if (this->expire_after_.has_value()) { + if (this->expire_after_.has_value()) return *this->expire_after_; - } else { -#ifdef USE_DEEP_SLEEP - if (deep_sleep::global_has_deep_sleep) { - return 0; - } -#endif - return this->sensor_->calculate_expected_filter_update_interval() * 5; - } + return 0; } void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire_after_ = expire_after; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } @@ -61,8 +54,8 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; - if (this->sensor_->state_class != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->state_class); + if (this->sensor_->get_state_class() != STATE_CLASS_NONE) + root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index bbe47b43ec..f048189959 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -8,7 +8,6 @@ namespace sensor { static const char *const TAG = "sensor.filter"; // Filter -uint32_t Filter::expected_interval(uint32_t input) { return input; } void Filter::input(float value) { ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value); optional out = this->new_value(value); @@ -29,15 +28,6 @@ void Filter::initialize(Sensor *parent, Filter *next) { this->parent_ = parent; this->next_ = next; } -uint32_t Filter::calculate_remaining_interval(uint32_t input) { - uint32_t this_interval = this->expected_interval(input); - ESP_LOGVV(TAG, "Filter(%p)::calculate_remaining_interval(%u) -> %u", this, input, this_interval); - if (this->next_ == nullptr) { - return this_interval; - } else { - return this->next_->calculate_remaining_interval(this_interval); - } -} // MedianFilter MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -75,8 +65,6 @@ optional MedianFilter::new_value(float value) { return {}; } -uint32_t MedianFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MinFilter MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -106,8 +94,6 @@ optional MinFilter::new_value(float value) { return {}; } -uint32_t MinFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // MaxFilter MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at) : send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {} @@ -137,8 +123,6 @@ optional MaxFilter::new_value(float value) { return {}; } -uint32_t MaxFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // SlidingWindowMovingAverageFilter SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window_size, size_t send_every, size_t send_first_at) @@ -177,8 +161,6 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { return {}; } -uint32_t SlidingWindowMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } - // ExponentialMovingAverageFilter ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} @@ -203,7 +185,6 @@ optional ExponentialMovingAverageFilter::new_value(float value) { } void ExponentialMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void ExponentialMovingAverageFilter::set_alpha(float alpha) { this->alpha_ = alpha; } -uint32_t ExponentialMovingAverageFilter::expected_interval(uint32_t input) { return input * this->send_every_; } // LambdaFilter LambdaFilter::LambdaFilter(lambda_filter_t lambda_filter) : lambda_filter_(std::move(lambda_filter)) {} @@ -296,14 +277,6 @@ void OrFilter::initialize(Sensor *parent, Filter *next) { this->phi_.initialize(parent, nullptr); } -uint32_t OrFilter::expected_interval(uint32_t input) { - uint32_t min_interval = UINT32_MAX; - for (Filter *filter : this->filters_) { - min_interval = std::min(min_interval, filter->calculate_remaining_interval(input)); - } - - return min_interval; -} // DebounceFilter optional DebounceFilter::new_value(float value) { this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); }); @@ -324,7 +297,6 @@ optional HeartbeatFilter::new_value(float value) { return {}; } -uint32_t HeartbeatFilter::expected_interval(uint32_t input) { return this->time_period_; } void HeartbeatFilter::setup() { this->set_interval("heartbeat", this->time_period_, [this]() { ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_), diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 270a91ccff..29a6813ea9 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -33,11 +33,6 @@ class Filter { void input(float value); - /// Return the amount of time that this filter is expected to take based on the input time interval. - virtual uint32_t expected_interval(uint32_t input); - - uint32_t calculate_remaining_interval(uint32_t input); - void output(float value); protected: @@ -68,8 +63,6 @@ class MedianFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -98,8 +91,6 @@ class MinFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -128,8 +119,6 @@ class MaxFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: std::deque queue_; size_t send_every_; @@ -159,8 +148,6 @@ class SlidingWindowMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_window_size(size_t window_size); - uint32_t expected_interval(uint32_t input) override; - protected: float sum_{0.0}; std::deque queue_; @@ -183,8 +170,6 @@ class ExponentialMovingAverageFilter : public Filter { void set_send_every(size_t send_every); void set_alpha(float alpha); - uint32_t expected_interval(uint32_t input) override; - protected: bool first_value_{true}; float accumulator_{0.0f}; @@ -279,8 +264,6 @@ class HeartbeatFilter : public Filter, public Component { optional new_value(float value) override; - uint32_t expected_interval(uint32_t input) override; - float get_setup_priority() const override; protected: @@ -306,8 +289,6 @@ class OrFilter : public Filter { void initialize(Sensor *parent, Filter *next) override; - uint32_t expected_interval(uint32_t input) override; - optional new_value(float value) override; protected: diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 34f72a4508..128f36fc93 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -18,6 +18,51 @@ const LogString *state_class_to_string(StateClass state_class) { } } +Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} +Sensor::Sensor() : Sensor("") {} + +std::string Sensor::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return this->unit_of_measurement(); +} +void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} +std::string Sensor::unit_of_measurement() { return ""; } + +std::string Sensor::get_icon() { + if (this->icon_.has_value()) + return *this->icon_; + return this->icon(); +} +void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } +std::string Sensor::icon() { return ""; } + +int8_t Sensor::get_accuracy_decimals() { + if (this->accuracy_decimals_.has_value()) + return *this->accuracy_decimals_; + return this->accuracy_decimals(); +} +void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } +int8_t Sensor::accuracy_decimals() { return 0; } + +std::string Sensor::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return this->device_class(); +} +void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } +std::string Sensor::device_class() { return ""; } + +void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } +StateClass Sensor::get_state_class() { + if (this->state_class_.has_value()) + return *this->state_class_; + return this->state_class(); +} +StateClass Sensor::state_class() { return StateClass::STATE_CLASS_NONE; } + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -30,54 +75,12 @@ void Sensor::publish_state(float state) { this->filter_list_->input(state); } } -std::string Sensor::unit_of_measurement() { return ""; } -std::string Sensor::icon() { return ""; } -uint32_t Sensor::update_interval() { return 0; } -int8_t Sensor::accuracy_decimals() { return 0; } -Sensor::Sensor(const std::string &name) : Nameable(name), state(NAN), raw_state(NAN) {} -Sensor::Sensor() : Sensor("") {} -void Sensor::set_unit_of_measurement(const std::string &unit_of_measurement) { - this->unit_of_measurement_ = unit_of_measurement; -} -void Sensor::set_icon(const std::string &icon) { this->icon_ = icon; } -void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { this->raw_callback_.add(std::move(callback)); } -std::string Sensor::get_icon() { - if (this->icon_.has_value()) - return *this->icon_; - return this->icon(); -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return this->device_class(); -} -std::string Sensor::device_class() { return ""; } -void Sensor::set_state_class(StateClass state_class) { this->state_class = state_class; } -void Sensor::set_state_class(const std::string &state_class) { - if (str_equals_case_insensitive(state_class, "measurement")) { - this->state_class = STATE_CLASS_MEASUREMENT; - } else if (str_equals_case_insensitive(state_class, "total_increasing")) { - this->state_class = STATE_CLASS_TOTAL_INCREASING; - } else { - ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); - } -} -std::string Sensor::get_unit_of_measurement() { - if (this->unit_of_measurement_.has_value()) - return *this->unit_of_measurement_; - return this->unit_of_measurement(); -} -int8_t Sensor::get_accuracy_decimals() { - if (this->accuracy_decimals_.has_value()) - return *this->accuracy_decimals_; - return this->accuracy_decimals(); -} + void Sensor::add_filter(Filter *filter) { // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of // filters @@ -119,24 +122,7 @@ void Sensor::internal_send_state_to_frontend(float state) { this->callback_.call(state); } bool Sensor::has_state() const { return this->has_state_; } -uint32_t Sensor::calculate_expected_filter_update_interval() { - uint32_t interval = this->update_interval(); - if (interval == 4294967295UL) - // update_interval: never - return 0; - - if (this->filter_list_ == nullptr) { - return interval; - } - - return this->filter_list_->calculate_remaining_interval(interval); -} uint32_t Sensor::hash_base() { return 2455723294UL; } -PollingSensorComponent::PollingSensorComponent(const std::string &name, uint32_t update_interval) - : PollingComponent(update_interval), Sensor(name) {} - -uint32_t PollingSensorComponent::update_interval() { return this->get_update_interval(); } - } // namespace sensor } // namespace esphome diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 6322c12027..3fc993cb2c 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,7 +14,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->state_class))); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->get_state_class()))); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -48,26 +48,42 @@ class Sensor : public Nameable { explicit Sensor(); explicit Sensor(const std::string &name); - /** Manually set the unit of measurement of this sensor. By default the sensor's default defined by - * unit_of_measurement() is used. - * - * @param unit_of_measurement The unit of measurement, "" to disable. - */ + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + /// Manually set the unit of measurement. void set_unit_of_measurement(const std::string &unit_of_measurement); - /** Manually set the icon of this sensor. By default the sensor's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ + /// Get the icon. Uses the manual override if specified or the default value instead. + std::string get_icon(); + /// Manually set the icon, for example "mdi:flash". void set_icon(const std::string &icon); - /** Manually set the accuracy in decimals for this sensor. By default, the sensor's default defined by - * accuracy_decimals() is used. - * - * @param accuracy_decimals The accuracy decimal that should be used. - */ + /// Get the accuracy in decimals, using the manual override if set. + int8_t get_accuracy_decimals(); + /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const std::string &device_class); + + /// Get the state class, using the manual override if set. + StateClass get_state_class(); + /// Manually set the state class. + void set_state_class(StateClass state_class); + + /** + * Get whether force update mode is enabled. + * + * If the sensor is in force_update mode, the frontend is required to save all + * state changes to the database when they are published, even if the state is the + * same as before. + */ + bool get_force_update() const { return force_update_; } + /// Set force update mode. + void set_force_update(bool force_update) { force_update_ = force_update; } + /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -94,15 +110,6 @@ class Sensor : public Nameable { /// Getter-syntax for .raw_state float get_raw_state() const; - /// Get the accuracy in decimals. Uses the manual override if specified or the default value instead. - int8_t get_accuracy_decimals(); - - /// Get the unit of measurement. Uses the manual override if specified or the default value instead. - std::string get_unit_of_measurement(); - - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - /** Publish a new state to the front-end. * * First, the new state will be assigned to the raw_value. Then it's passed through all filters @@ -128,35 +135,15 @@ class Sensor : public Nameable { */ float state; - /// Manually set the Home Assistant device class (see sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this sensor, using the manual override if specified. - std::string get_device_class(); - - /** This member variable stores the current raw state of the sensor. Unlike .state, - * this will be updated immediately when publish_state is called. + /** This member variable stores the current raw state of the sensor, without any filters applied. + * + * Unlike .state,this will be updated immediately when publish_state is called. */ float raw_state; /// Return whether this sensor has gotten a full state (that passed through all filters) yet. bool has_state() const; - // The state class of this sensor state - StateClass state_class{STATE_CLASS_NONE}; - - /// Manually set the Home Assistant state class (see sensor::state_class) - void set_state_class(StateClass state_class); - void set_state_class(const std::string &state_class); - - /** Override this to set the Home Assistant device class for this sensor. - * - * Return "" to disable this feature. - * - * @return The device class of this sensor, for example "temperature". - */ - virtual std::string device_class(); - /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * @@ -164,65 +151,38 @@ class Sensor : public Nameable { */ virtual std::string unique_id(); - /// Return with which interval the sensor is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - /// Calculate the expected update interval for values that pass through all filters. - uint32_t calculate_expected_filter_update_interval(); - void internal_send_state_to_frontend(float state); - bool get_force_update() const { return force_update_; } - /** Set this sensor's force_update mode. - * - * If the sensor is in force_update mode, the frontend is required to save all - * state changes to the database when they are published, even if the state is the - * same as before. - */ - void set_force_update(bool force_update) { force_update_ = force_update; } - protected: - /** Override this to set the Home Assistant unit of measurement for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "°C". - */ + /// Override this to set the default unit of measurement. virtual std::string unit_of_measurement(); // NOLINT - /** Override this to set the Home Assistant icon for this sensor. - * - * Return "" to disable this feature. - * - * @return The icon of this sensor, for example "mdi:battery". - */ + /// Override this to set the default icon. virtual std::string icon(); // NOLINT - /// Return the accuracy in decimals for this sensor. + /// Override this to set the default accuracy in decimals. virtual int8_t accuracy_decimals(); // NOLINT - optional device_class_{}; ///< Stores the override of the device class + /// Override this to set the default device class. + virtual std::string device_class(); // NOLINT + + /// Override this to set the default state class. + virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; CallbackManager raw_callback_; ///< Storage for raw state callbacks. CallbackManager callback_; ///< Storage for filtered state callbacks. - /// Override the unit of measurement - optional unit_of_measurement_; - /// Override the icon advertised to Home Assistant, otherwise sensor's icon will be used. - optional icon_; - /// Override the accuracy in decimals, otherwise the sensor's values will be used. - optional accuracy_decimals_; - Filter *filter_list_{nullptr}; ///< Store all active filters. + bool has_state_{false}; - bool force_update_{false}; -}; + Filter *filter_list_{nullptr}; ///< Store all active filters. -class PollingSensorComponent : public PollingComponent, public Sensor { - public: - explicit PollingSensorComponent(const std::string &name, uint32_t update_interval); - - uint32_t update_interval() override; + optional unit_of_measurement_; ///< Unit of measurement override + optional icon_; ///< Icon override + optional accuracy_decimals_; ///< Accuracy in decimals override + optional device_class_; ///< Device class override + optional state_class_{STATE_CLASS_NONE}; ///< State class override + bool force_update_{false}; ///< Force update mode }; } // namespace sensor From b9767bdcbcb00aca39ea8f0cf63f3fbcbe142649 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 13 Sep 2021 21:16:13 +0200 Subject: [PATCH 224/285] Bump platformio to 5.2.0 (#2291) --- docker/build.py | 2 +- esphome/zeroconf.py | 7 ++++--- requirements.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/build.py b/docker/build.py index 039d7dc15b..c926b3653b 100755 --- a/docker/build.py +++ b/docker/build.py @@ -24,7 +24,7 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "4.1.1" +BASE_VERSION = "4.2.0" parser = argparse.ArgumentParser() diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index e94b59d3ae..443ed6a33a 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -4,9 +4,6 @@ import time from typing import Dict, Optional from zeroconf import ( - _CLASS_IN, - _FLAGS_QR_QUERY, - _TYPE_A, DNSAddress, DNSOutgoing, DNSRecord, @@ -15,6 +12,10 @@ from zeroconf import ( Zeroconf, ) +_CLASS_IN = 1 +_FLAGS_QR_QUERY = 0x0000 # query +_TYPE_A = 1 + class HostResolver(RecordUpdateListener): def __init__(self, name: str): diff --git a/requirements.txt b/requirements.txt index daaf86e641..2d354d5f04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzlocal==2.1 pytz==2021.1 pyserial==3.5 ifaddr==0.1.7 -platformio==5.1.1 +platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 From 855112dfc3b3005b55f28c2e79859b072e86c3c8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 09:53:37 +0200 Subject: [PATCH 225/285] API Noise logging (#2298) --- esphome/__main__.py | 2 +- esphome/api/__init__.py | 0 esphome/api/api_pb2.py | 3997 ------------------------------ esphome/api/client.py | 518 ---- esphome/components/api/client.py | 73 + requirements.txt | 3 +- 6 files changed, 75 insertions(+), 4518 deletions(-) delete mode 100644 esphome/api/__init__.py delete mode 100644 esphome/api/api_pb2.py delete mode 100644 esphome/api/client.py create mode 100644 esphome/components/api/client.py diff --git a/esphome/__main__.py b/esphome/__main__.py index 97b2988c2e..121fa7cc9e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -256,7 +256,7 @@ def show_logs(config, args, port): run_miniterm(config, port) return 0 if get_port_type(port) == "NETWORK" and "api" in config: - from esphome.api.client import run_logs + from esphome.components.api.client import run_logs return run_logs(config, port) if get_port_type(port) == "MQTT" and "mqtt" in config: diff --git a/esphome/api/__init__.py b/esphome/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esphome/api/api_pb2.py b/esphome/api/api_pb2.py deleted file mode 100644 index 6262b752c6..0000000000 --- a/esphome/api/api_pb2.py +++ /dev/null @@ -1,3997 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: api.proto - -import sys - -_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1")) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="api.proto", - package="", - syntax="proto3", - serialized_options=None, - serialized_pb=_b( - '\n\tapi.proto"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t""\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08"\x13\n\x11\x44isconnectRequest"\x14\n\x12\x44isconnectResponse"\r\n\x0bPingRequest"\x0e\n\x0cPingResponse"\x13\n\x11\x44\x65viceInfoRequest"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08"\x15\n\x13ListEntitiesRequest"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t"\x1a\n\x18ListEntitiesDoneResponse"\x18\n\x16SubscribeStatesRequest"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState""\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08"\x1e\n\x1cSubscribeServiceCallsRequest"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"%\n#SubscribeHomeAssistantStatesRequest"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t"\x10\n\x0eGetTimeRequest"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3' - ), -) - -_FANSPEED = _descriptor.EnumDescriptor( - name="FanSpeed", - full_name="FanSpeed", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="LOW", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="MEDIUM", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="HIGH", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3822, - serialized_end=3863, -) -_sym_db.RegisterEnumDescriptor(_FANSPEED) - -FanSpeed = enum_type_wrapper.EnumTypeWrapper(_FANSPEED) -_LOGLEVEL = _descriptor.EnumDescriptor( - name="LogLevel", - full_name="LogLevel", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="NONE", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="ERROR", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="WARN", index=2, number=2, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="INFO", index=3, number=3, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="DEBUG", index=4, number=4, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERBOSE", index=5, number=5, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="VERY_VERBOSE", index=6, number=6, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=3865, - serialized_end=3958, -) -_sym_db.RegisterEnumDescriptor(_LOGLEVEL) - -LogLevel = enum_type_wrapper.EnumTypeWrapper(_LOGLEVEL) -LOW = 0 -MEDIUM = 1 -HIGH = 2 -NONE = 0 -ERROR = 1 -WARN = 2 -INFO = 3 -DEBUG = 4 -VERBOSE = 5 -VERY_VERBOSE = 6 - - -_COVERSTATERESPONSE_COVERSTATE = _descriptor.EnumDescriptor( - name="CoverState", - full_name="CoverStateResponse.CoverState", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSED", index=1, number=1, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=1808, - serialized_end=1842, -) -_sym_db.RegisterEnumDescriptor(_COVERSTATERESPONSE_COVERSTATE) - -_COVERCOMMANDREQUEST_COVERCOMMAND = _descriptor.EnumDescriptor( - name="CoverCommand", - full_name="CoverCommandRequest.CoverCommand", - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name="OPEN", index=0, number=0, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="CLOSE", index=1, number=1, serialized_options=None, type=None - ), - _descriptor.EnumValueDescriptor( - name="STOP", index=2, number=2, serialized_options=None, type=None - ), - ], - containing_type=None, - serialized_options=None, - serialized_start=2375, - serialized_end=2420, -) -_sym_db.RegisterEnumDescriptor(_COVERCOMMANDREQUEST_COVERCOMMAND) - - -_HELLOREQUEST = _descriptor.Descriptor( - name="HelloRequest", - full_name="HelloRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="client_info", - full_name="HelloRequest.client_info", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=13, - serialized_end=48, -) - - -_HELLORESPONSE = _descriptor.Descriptor( - name="HelloResponse", - full_name="HelloResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="api_version_major", - full_name="HelloResponse.api_version_major", - index=0, - number=1, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="api_version_minor", - full_name="HelloResponse.api_version_minor", - index=1, - number=2, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="server_info", - full_name="HelloResponse.server_info", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=50, - serialized_end=140, -) - - -_CONNECTREQUEST = _descriptor.Descriptor( - name="ConnectRequest", - full_name="ConnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="password", - full_name="ConnectRequest.password", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=142, - serialized_end=176, -) - - -_CONNECTRESPONSE = _descriptor.Descriptor( - name="ConnectResponse", - full_name="ConnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="invalid_password", - full_name="ConnectResponse.invalid_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=178, - serialized_end=221, -) - - -_DISCONNECTREQUEST = _descriptor.Descriptor( - name="DisconnectRequest", - full_name="DisconnectRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=223, - serialized_end=242, -) - - -_DISCONNECTRESPONSE = _descriptor.Descriptor( - name="DisconnectResponse", - full_name="DisconnectResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=244, - serialized_end=264, -) - - -_PINGREQUEST = _descriptor.Descriptor( - name="PingRequest", - full_name="PingRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=266, - serialized_end=279, -) - - -_PINGRESPONSE = _descriptor.Descriptor( - name="PingResponse", - full_name="PingResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=281, - serialized_end=295, -) - - -_DEVICEINFOREQUEST = _descriptor.Descriptor( - name="DeviceInfoRequest", - full_name="DeviceInfoRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=297, - serialized_end=316, -) - - -_DEVICEINFORESPONSE = _descriptor.Descriptor( - name="DeviceInfoResponse", - full_name="DeviceInfoResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="uses_password", - full_name="DeviceInfoResponse.uses_password", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="DeviceInfoResponse.name", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="mac_address", - full_name="DeviceInfoResponse.mac_address", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="esphome_core_version", - full_name="DeviceInfoResponse.esphome_core_version", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="compilation_time", - full_name="DeviceInfoResponse.compilation_time", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="model", - full_name="DeviceInfoResponse.model", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_deep_sleep", - full_name="DeviceInfoResponse.has_deep_sleep", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=319, - serialized_end=492, -) - - -_LISTENTITIESREQUEST = _descriptor.Descriptor( - name="ListEntitiesRequest", - full_name="ListEntitiesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=494, - serialized_end=515, -) - - -_LISTENTITIESBINARYSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesBinarySensorResponse", - full_name="ListEntitiesBinarySensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesBinarySensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesBinarySensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesBinarySensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesBinarySensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="device_class", - full_name="ListEntitiesBinarySensorResponse.device_class", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_status_binary_sensor", - full_name="ListEntitiesBinarySensorResponse.is_status_binary_sensor", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=518, - serialized_end=672, -) - - -_LISTENTITIESCOVERRESPONSE = _descriptor.Descriptor( - name="ListEntitiesCoverResponse", - full_name="ListEntitiesCoverResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesCoverResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesCoverResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesCoverResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesCoverResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="is_optimistic", - full_name="ListEntitiesCoverResponse.is_optimistic", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=674, - serialized_end=789, -) - - -_LISTENTITIESFANRESPONSE = _descriptor.Descriptor( - name="ListEntitiesFanResponse", - full_name="ListEntitiesFanResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesFanResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesFanResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesFanResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesFanResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_oscillation", - full_name="ListEntitiesFanResponse.supports_oscillation", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_speed", - full_name="ListEntitiesFanResponse.supports_speed", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=792, - serialized_end=936, -) - - -_LISTENTITIESLIGHTRESPONSE = _descriptor.Descriptor( - name="ListEntitiesLightResponse", - full_name="ListEntitiesLightResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesLightResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesLightResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesLightResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesLightResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_brightness", - full_name="ListEntitiesLightResponse.supports_brightness", - index=4, - number=5, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_rgb", - full_name="ListEntitiesLightResponse.supports_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_white_value", - full_name="ListEntitiesLightResponse.supports_white_value", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="supports_color_temperature", - full_name="ListEntitiesLightResponse.supports_color_temperature", - index=7, - number=8, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="min_mireds", - full_name="ListEntitiesLightResponse.min_mireds", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="max_mireds", - full_name="ListEntitiesLightResponse.max_mireds", - index=9, - number=10, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effects", - full_name="ListEntitiesLightResponse.effects", - index=10, - number=11, - type=9, - cpp_type=9, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=939, - serialized_end=1205, -) - - -_LISTENTITIESSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSensorResponse", - full_name="ListEntitiesSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unit_of_measurement", - full_name="ListEntitiesSensorResponse.unit_of_measurement", - index=5, - number=6, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="accuracy_decimals", - full_name="ListEntitiesSensorResponse.accuracy_decimals", - index=6, - number=7, - type=5, - cpp_type=1, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1208, - serialized_end=1371, -) - - -_LISTENTITIESSWITCHRESPONSE = _descriptor.Descriptor( - name="ListEntitiesSwitchResponse", - full_name="ListEntitiesSwitchResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesSwitchResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesSwitchResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesSwitchResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesSwitchResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesSwitchResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="optimistic", - full_name="ListEntitiesSwitchResponse.optimistic", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1373, - serialized_end=1500, -) - - -_LISTENTITIESTEXTSENSORRESPONSE = _descriptor.Descriptor( - name="ListEntitiesTextSensorResponse", - full_name="ListEntitiesTextSensorResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="object_id", - full_name="ListEntitiesTextSensorResponse.object_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="key", - full_name="ListEntitiesTextSensorResponse.key", - index=1, - number=2, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="name", - full_name="ListEntitiesTextSensorResponse.name", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="unique_id", - full_name="ListEntitiesTextSensorResponse.unique_id", - index=3, - number=4, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="icon", - full_name="ListEntitiesTextSensorResponse.icon", - index=4, - number=5, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1502, - serialized_end=1613, -) - - -_LISTENTITIESDONERESPONSE = _descriptor.Descriptor( - name="ListEntitiesDoneResponse", - full_name="ListEntitiesDoneResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1615, - serialized_end=1641, -) - - -_SUBSCRIBESTATESREQUEST = _descriptor.Descriptor( - name="SubscribeStatesRequest", - full_name="SubscribeStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1643, - serialized_end=1667, -) - - -_BINARYSENSORSTATERESPONSE = _descriptor.Descriptor( - name="BinarySensorStateResponse", - full_name="BinarySensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="BinarySensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="BinarySensorStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1669, - serialized_end=1724, -) - - -_COVERSTATERESPONSE = _descriptor.Descriptor( - name="CoverStateResponse", - full_name="CoverStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="CoverStateResponse.state", - index=1, - number=2, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERSTATERESPONSE_COVERSTATE, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1726, - serialized_end=1842, -) - - -_FANSTATERESPONSE = _descriptor.Descriptor( - name="FanStateResponse", - full_name="FanStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanStateResponse.oscillating", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanStateResponse.speed", - index=3, - number=4, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1844, - serialized_end=1937, -) - - -_LIGHTSTATERESPONSE = _descriptor.Descriptor( - name="LightStateResponse", - full_name="LightStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightStateResponse.brightness", - index=2, - number=3, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightStateResponse.red", - index=3, - number=4, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightStateResponse.green", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightStateResponse.blue", - index=5, - number=6, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightStateResponse.white", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightStateResponse.color_temperature", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightStateResponse.effect", - index=8, - number=9, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=1940, - serialized_end=2108, -) - - -_SENSORSTATERESPONSE = _descriptor.Descriptor( - name="SensorStateResponse", - full_name="SensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SensorStateResponse.state", - index=1, - number=2, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2110, - serialized_end=2159, -) - - -_SWITCHSTATERESPONSE = _descriptor.Descriptor( - name="SwitchStateResponse", - full_name="SwitchStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchStateResponse.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2161, - serialized_end=2210, -) - - -_TEXTSENSORSTATERESPONSE = _descriptor.Descriptor( - name="TextSensorStateResponse", - full_name="TextSensorStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="TextSensorStateResponse.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="TextSensorStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2212, - serialized_end=2265, -) - - -_COVERCOMMANDREQUEST = _descriptor.Descriptor( - name="CoverCommandRequest", - full_name="CoverCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="CoverCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="CoverCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="command", - full_name="CoverCommandRequest.command", - index=2, - number=3, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[ - _COVERCOMMANDREQUEST_COVERCOMMAND, - ], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2268, - serialized_end=2420, -) - - -_FANCOMMANDREQUEST = _descriptor.Descriptor( - name="FanCommandRequest", - full_name="FanCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="FanCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="FanCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="FanCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_speed", - full_name="FanCommandRequest.has_speed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="speed", - full_name="FanCommandRequest.speed", - index=4, - number=5, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_oscillating", - full_name="FanCommandRequest.has_oscillating", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="oscillating", - full_name="FanCommandRequest.oscillating", - index=6, - number=7, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2423, - serialized_end=2580, -) - - -_LIGHTCOMMANDREQUEST = _descriptor.Descriptor( - name="LightCommandRequest", - full_name="LightCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="LightCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_state", - full_name="LightCommandRequest.has_state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="LightCommandRequest.state", - index=2, - number=3, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_brightness", - full_name="LightCommandRequest.has_brightness", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="brightness", - full_name="LightCommandRequest.brightness", - index=4, - number=5, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_rgb", - full_name="LightCommandRequest.has_rgb", - index=5, - number=6, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="red", - full_name="LightCommandRequest.red", - index=6, - number=7, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="green", - full_name="LightCommandRequest.green", - index=7, - number=8, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="blue", - full_name="LightCommandRequest.blue", - index=8, - number=9, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_white", - full_name="LightCommandRequest.has_white", - index=9, - number=10, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="white", - full_name="LightCommandRequest.white", - index=10, - number=11, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_color_temperature", - full_name="LightCommandRequest.has_color_temperature", - index=11, - number=12, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="color_temperature", - full_name="LightCommandRequest.color_temperature", - index=12, - number=13, - type=2, - cpp_type=6, - label=1, - has_default_value=False, - default_value=float(0), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_transition_length", - full_name="LightCommandRequest.has_transition_length", - index=13, - number=14, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="transition_length", - full_name="LightCommandRequest.transition_length", - index=14, - number=15, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_flash_length", - full_name="LightCommandRequest.has_flash_length", - index=15, - number=16, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="flash_length", - full_name="LightCommandRequest.flash_length", - index=16, - number=17, - type=13, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="has_effect", - full_name="LightCommandRequest.has_effect", - index=17, - number=18, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="effect", - full_name="LightCommandRequest.effect", - index=18, - number=19, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2583, - serialized_end=2988, -) - - -_SWITCHCOMMANDREQUEST = _descriptor.Descriptor( - name="SwitchCommandRequest", - full_name="SwitchCommandRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="SwitchCommandRequest.key", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="SwitchCommandRequest.state", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2990, - serialized_end=3040, -) - - -_SUBSCRIBELOGSREQUEST = _descriptor.Descriptor( - name="SubscribeLogsRequest", - full_name="SubscribeLogsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsRequest.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="dump_config", - full_name="SubscribeLogsRequest.dump_config", - index=1, - number=2, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3042, - serialized_end=3111, -) - - -_SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor( - name="SubscribeLogsResponse", - full_name="SubscribeLogsResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="level", - full_name="SubscribeLogsResponse.level", - index=0, - number=1, - type=14, - cpp_type=8, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="tag", - full_name="SubscribeLogsResponse.tag", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="message", - full_name="SubscribeLogsResponse.message", - index=2, - number=3, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="send_failed", - full_name="SubscribeLogsResponse.send_failed", - index=3, - number=4, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3113, - serialized_end=3213, -) - - -_SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor( - name="SubscribeServiceCallsRequest", - full_name="SubscribeServiceCallsRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3215, - serialized_end=3245, -) - - -_SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor( - name="DataEntry", - full_name="ServiceCallResponse.DataEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3453, - serialized_end=3496, -) - -_SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor( - name="DataTemplateEntry", - full_name="ServiceCallResponse.DataTemplateEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.DataTemplateEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.DataTemplateEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3498, - serialized_end=3549, -) - -_SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor( - name="VariablesEntry", - full_name="ServiceCallResponse.VariablesEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="ServiceCallResponse.VariablesEntry.key", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="ServiceCallResponse.VariablesEntry.value", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3551, - serialized_end=3599, -) - -_SERVICECALLRESPONSE = _descriptor.Descriptor( - name="ServiceCallResponse", - full_name="ServiceCallResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="service", - full_name="ServiceCallResponse.service", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data", - full_name="ServiceCallResponse.data", - index=1, - number=2, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="data_template", - full_name="ServiceCallResponse.data_template", - index=2, - number=3, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="variables", - full_name="ServiceCallResponse.variables", - index=3, - number=4, - type=11, - cpp_type=10, - label=3, - has_default_value=False, - default_value=[], - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[ - _SERVICECALLRESPONSE_DATAENTRY, - _SERVICECALLRESPONSE_DATATEMPLATEENTRY, - _SERVICECALLRESPONSE_VARIABLESENTRY, - ], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3248, - serialized_end=3599, -) - - -_SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor( - name="SubscribeHomeAssistantStatesRequest", - full_name="SubscribeHomeAssistantStatesRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3601, - serialized_end=3638, -) - - -_SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="SubscribeHomeAssistantStateResponse", - full_name="SubscribeHomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="SubscribeHomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3640, - serialized_end=3696, -) - - -_HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor( - name="HomeAssistantStateResponse", - full_name="HomeAssistantStateResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="entity_id", - full_name="HomeAssistantStateResponse.entity_id", - index=0, - number=1, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="state", - full_name="HomeAssistantStateResponse.state", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=_b("").decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3698, - serialized_end=3760, -) - - -_GETTIMEREQUEST = _descriptor.Descriptor( - name="GetTimeRequest", - full_name="GetTimeRequest", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3762, - serialized_end=3778, -) - - -_GETTIMERESPONSE = _descriptor.Descriptor( - name="GetTimeResponse", - full_name="GetTimeResponse", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="epoch_seconds", - full_name="GetTimeResponse.epoch_seconds", - index=0, - number=1, - type=7, - cpp_type=3, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=3780, - serialized_end=3820, -) - -_COVERSTATERESPONSE.fields_by_name["state"].enum_type = _COVERSTATERESPONSE_COVERSTATE -_COVERSTATERESPONSE_COVERSTATE.containing_type = _COVERSTATERESPONSE -_FANSTATERESPONSE.fields_by_name["speed"].enum_type = _FANSPEED -_COVERCOMMANDREQUEST.fields_by_name[ - "command" -].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND -_COVERCOMMANDREQUEST_COVERCOMMAND.containing_type = _COVERCOMMANDREQUEST -_FANCOMMANDREQUEST.fields_by_name["speed"].enum_type = _FANSPEED -_SUBSCRIBELOGSREQUEST.fields_by_name["level"].enum_type = _LOGLEVEL -_SUBSCRIBELOGSRESPONSE.fields_by_name["level"].enum_type = _LOGLEVEL -_SERVICECALLRESPONSE_DATAENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_DATATEMPLATEENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE_VARIABLESENTRY.containing_type = _SERVICECALLRESPONSE -_SERVICECALLRESPONSE.fields_by_name[ - "data" -].message_type = _SERVICECALLRESPONSE_DATAENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "data_template" -].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY -_SERVICECALLRESPONSE.fields_by_name[ - "variables" -].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY -DESCRIPTOR.message_types_by_name["HelloRequest"] = _HELLOREQUEST -DESCRIPTOR.message_types_by_name["HelloResponse"] = _HELLORESPONSE -DESCRIPTOR.message_types_by_name["ConnectRequest"] = _CONNECTREQUEST -DESCRIPTOR.message_types_by_name["ConnectResponse"] = _CONNECTRESPONSE -DESCRIPTOR.message_types_by_name["DisconnectRequest"] = _DISCONNECTREQUEST -DESCRIPTOR.message_types_by_name["DisconnectResponse"] = _DISCONNECTRESPONSE -DESCRIPTOR.message_types_by_name["PingRequest"] = _PINGREQUEST -DESCRIPTOR.message_types_by_name["PingResponse"] = _PINGRESPONSE -DESCRIPTOR.message_types_by_name["DeviceInfoRequest"] = _DEVICEINFOREQUEST -DESCRIPTOR.message_types_by_name["DeviceInfoResponse"] = _DEVICEINFORESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesRequest"] = _LISTENTITIESREQUEST -DESCRIPTOR.message_types_by_name[ - "ListEntitiesBinarySensorResponse" -] = _LISTENTITIESBINARYSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesCoverResponse" -] = _LISTENTITIESCOVERRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesFanResponse"] = _LISTENTITIESFANRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesLightResponse" -] = _LISTENTITIESLIGHTRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSensorResponse" -] = _LISTENTITIESSENSORRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesSwitchResponse" -] = _LISTENTITIESSWITCHRESPONSE -DESCRIPTOR.message_types_by_name[ - "ListEntitiesTextSensorResponse" -] = _LISTENTITIESTEXTSENSORRESPONSE -DESCRIPTOR.message_types_by_name["ListEntitiesDoneResponse"] = _LISTENTITIESDONERESPONSE -DESCRIPTOR.message_types_by_name["SubscribeStatesRequest"] = _SUBSCRIBESTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "BinarySensorStateResponse" -] = _BINARYSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverStateResponse"] = _COVERSTATERESPONSE -DESCRIPTOR.message_types_by_name["FanStateResponse"] = _FANSTATERESPONSE -DESCRIPTOR.message_types_by_name["LightStateResponse"] = _LIGHTSTATERESPONSE -DESCRIPTOR.message_types_by_name["SensorStateResponse"] = _SENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["SwitchStateResponse"] = _SWITCHSTATERESPONSE -DESCRIPTOR.message_types_by_name["TextSensorStateResponse"] = _TEXTSENSORSTATERESPONSE -DESCRIPTOR.message_types_by_name["CoverCommandRequest"] = _COVERCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["FanCommandRequest"] = _FANCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["LightCommandRequest"] = _LIGHTCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SwitchCommandRequest"] = _SWITCHCOMMANDREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsRequest"] = _SUBSCRIBELOGSREQUEST -DESCRIPTOR.message_types_by_name["SubscribeLogsResponse"] = _SUBSCRIBELOGSRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeServiceCallsRequest" -] = _SUBSCRIBESERVICECALLSREQUEST -DESCRIPTOR.message_types_by_name["ServiceCallResponse"] = _SERVICECALLRESPONSE -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStatesRequest" -] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST -DESCRIPTOR.message_types_by_name[ - "SubscribeHomeAssistantStateResponse" -] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name[ - "HomeAssistantStateResponse" -] = _HOMEASSISTANTSTATERESPONSE -DESCRIPTOR.message_types_by_name["GetTimeRequest"] = _GETTIMEREQUEST -DESCRIPTOR.message_types_by_name["GetTimeResponse"] = _GETTIMERESPONSE -DESCRIPTOR.enum_types_by_name["FanSpeed"] = _FANSPEED -DESCRIPTOR.enum_types_by_name["LogLevel"] = _LOGLEVEL -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -HelloRequest = _reflection.GeneratedProtocolMessageType( - "HelloRequest", - (_message.Message,), - dict( - DESCRIPTOR=_HELLOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloRequest) - ), -) -_sym_db.RegisterMessage(HelloRequest) - -HelloResponse = _reflection.GeneratedProtocolMessageType( - "HelloResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HELLORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HelloResponse) - ), -) -_sym_db.RegisterMessage(HelloResponse) - -ConnectRequest = _reflection.GeneratedProtocolMessageType( - "ConnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectRequest) - ), -) -_sym_db.RegisterMessage(ConnectRequest) - -ConnectResponse = _reflection.GeneratedProtocolMessageType( - "ConnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_CONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ConnectResponse) - ), -) -_sym_db.RegisterMessage(ConnectResponse) - -DisconnectRequest = _reflection.GeneratedProtocolMessageType( - "DisconnectRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectRequest) - ), -) -_sym_db.RegisterMessage(DisconnectRequest) - -DisconnectResponse = _reflection.GeneratedProtocolMessageType( - "DisconnectResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DISCONNECTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DisconnectResponse) - ), -) -_sym_db.RegisterMessage(DisconnectResponse) - -PingRequest = _reflection.GeneratedProtocolMessageType( - "PingRequest", - (_message.Message,), - dict( - DESCRIPTOR=_PINGREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingRequest) - ), -) -_sym_db.RegisterMessage(PingRequest) - -PingResponse = _reflection.GeneratedProtocolMessageType( - "PingResponse", - (_message.Message,), - dict( - DESCRIPTOR=_PINGRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:PingResponse) - ), -) -_sym_db.RegisterMessage(PingResponse) - -DeviceInfoRequest = _reflection.GeneratedProtocolMessageType( - "DeviceInfoRequest", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFOREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoRequest) - ), -) -_sym_db.RegisterMessage(DeviceInfoRequest) - -DeviceInfoResponse = _reflection.GeneratedProtocolMessageType( - "DeviceInfoResponse", - (_message.Message,), - dict( - DESCRIPTOR=_DEVICEINFORESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:DeviceInfoResponse) - ), -) -_sym_db.RegisterMessage(DeviceInfoResponse) - -ListEntitiesRequest = _reflection.GeneratedProtocolMessageType( - "ListEntitiesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesRequest) - ), -) -_sym_db.RegisterMessage(ListEntitiesRequest) - -ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesBinarySensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESBINARYSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesBinarySensorResponse) - -ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesCoverResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESCOVERRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesCoverResponse) - -ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesFanResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESFANRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesFanResponse) - -ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesLightResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESLIGHTRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesLightResponse) - -ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSensorResponse) - -ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesSwitchResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESSWITCHRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesSwitchResponse) - -ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesTextSensorResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESTEXTSENSORRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesTextSensorResponse) - -ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType( - "ListEntitiesDoneResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LISTENTITIESDONERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse) - ), -) -_sym_db.RegisterMessage(ListEntitiesDoneResponse) - -SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeStatesRequest) - -BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType( - "BinarySensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_BINARYSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:BinarySensorStateResponse) - ), -) -_sym_db.RegisterMessage(BinarySensorStateResponse) - -CoverStateResponse = _reflection.GeneratedProtocolMessageType( - "CoverStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_COVERSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverStateResponse) - ), -) -_sym_db.RegisterMessage(CoverStateResponse) - -FanStateResponse = _reflection.GeneratedProtocolMessageType( - "FanStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_FANSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanStateResponse) - ), -) -_sym_db.RegisterMessage(FanStateResponse) - -LightStateResponse = _reflection.GeneratedProtocolMessageType( - "LightStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightStateResponse) - ), -) -_sym_db.RegisterMessage(LightStateResponse) - -SensorStateResponse = _reflection.GeneratedProtocolMessageType( - "SensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SensorStateResponse) - ), -) -_sym_db.RegisterMessage(SensorStateResponse) - -SwitchStateResponse = _reflection.GeneratedProtocolMessageType( - "SwitchStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchStateResponse) - ), -) -_sym_db.RegisterMessage(SwitchStateResponse) - -TextSensorStateResponse = _reflection.GeneratedProtocolMessageType( - "TextSensorStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_TEXTSENSORSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:TextSensorStateResponse) - ), -) -_sym_db.RegisterMessage(TextSensorStateResponse) - -CoverCommandRequest = _reflection.GeneratedProtocolMessageType( - "CoverCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_COVERCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:CoverCommandRequest) - ), -) -_sym_db.RegisterMessage(CoverCommandRequest) - -FanCommandRequest = _reflection.GeneratedProtocolMessageType( - "FanCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_FANCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:FanCommandRequest) - ), -) -_sym_db.RegisterMessage(FanCommandRequest) - -LightCommandRequest = _reflection.GeneratedProtocolMessageType( - "LightCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_LIGHTCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:LightCommandRequest) - ), -) -_sym_db.RegisterMessage(LightCommandRequest) - -SwitchCommandRequest = _reflection.GeneratedProtocolMessageType( - "SwitchCommandRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SWITCHCOMMANDREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SwitchCommandRequest) - ), -) -_sym_db.RegisterMessage(SwitchCommandRequest) - -SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeLogsRequest) - -SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeLogsResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBELOGSRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeLogsResponse) - ), -) -_sym_db.RegisterMessage(SubscribeLogsResponse) - -SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeServiceCallsRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBESERVICECALLSREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest) - ), -) -_sym_db.RegisterMessage(SubscribeServiceCallsRequest) - -ServiceCallResponse = _reflection.GeneratedProtocolMessageType( - "ServiceCallResponse", - (_message.Message,), - dict( - DataEntry=_reflection.GeneratedProtocolMessageType( - "DataEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATAENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry) - ), - ), - DataTemplateEntry=_reflection.GeneratedProtocolMessageType( - "DataTemplateEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_DATATEMPLATEENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry) - ), - ), - VariablesEntry=_reflection.GeneratedProtocolMessageType( - "VariablesEntry", - (_message.Message,), - dict( - DESCRIPTOR=_SERVICECALLRESPONSE_VARIABLESENTRY, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry) - ), - ), - DESCRIPTOR=_SERVICECALLRESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:ServiceCallResponse) - ), -) -_sym_db.RegisterMessage(ServiceCallResponse) -_sym_db.RegisterMessage(ServiceCallResponse.DataEntry) -_sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry) -_sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry) - -SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStatesRequest", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATESREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest) - -SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "SubscribeHomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_SUBSCRIBEHOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse) - -HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType( - "HomeAssistantStateResponse", - (_message.Message,), - dict( - DESCRIPTOR=_HOMEASSISTANTSTATERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse) - ), -) -_sym_db.RegisterMessage(HomeAssistantStateResponse) - -GetTimeRequest = _reflection.GeneratedProtocolMessageType( - "GetTimeRequest", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMEREQUEST, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeRequest) - ), -) -_sym_db.RegisterMessage(GetTimeRequest) - -GetTimeResponse = _reflection.GeneratedProtocolMessageType( - "GetTimeResponse", - (_message.Message,), - dict( - DESCRIPTOR=_GETTIMERESPONSE, - __module__="api_pb2" - # @@protoc_insertion_point(class_scope:GetTimeResponse) - ), -) -_sym_db.RegisterMessage(GetTimeResponse) - - -_SERVICECALLRESPONSE_DATAENTRY._options = None -_SERVICECALLRESPONSE_DATATEMPLATEENTRY._options = None -_SERVICECALLRESPONSE_VARIABLESENTRY._options = None -# @@protoc_insertion_point(module_scope) diff --git a/esphome/api/client.py b/esphome/api/client.py deleted file mode 100644 index dd11f79922..0000000000 --- a/esphome/api/client.py +++ /dev/null @@ -1,518 +0,0 @@ -from datetime import datetime -import functools -import logging -import socket -import threading -import time - -# pylint: disable=unused-import -from typing import Optional # noqa -from google.protobuf import message # noqa - -from esphome import const -import esphome.api.api_pb2 as pb -from esphome.const import CONF_PASSWORD, CONF_PORT -from esphome.core import EsphomeError -from esphome.helpers import resolve_ip_address, indent -from esphome.log import color, Fore -from esphome.util import safe_print - -_LOGGER = logging.getLogger(__name__) - - -class APIConnectionError(EsphomeError): - pass - - -MESSAGE_TYPE_TO_PROTO = { - 1: pb.HelloRequest, - 2: pb.HelloResponse, - 3: pb.ConnectRequest, - 4: pb.ConnectResponse, - 5: pb.DisconnectRequest, - 6: pb.DisconnectResponse, - 7: pb.PingRequest, - 8: pb.PingResponse, - 9: pb.DeviceInfoRequest, - 10: pb.DeviceInfoResponse, - 11: pb.ListEntitiesRequest, - 12: pb.ListEntitiesBinarySensorResponse, - 13: pb.ListEntitiesCoverResponse, - 14: pb.ListEntitiesFanResponse, - 15: pb.ListEntitiesLightResponse, - 16: pb.ListEntitiesSensorResponse, - 17: pb.ListEntitiesSwitchResponse, - 18: pb.ListEntitiesTextSensorResponse, - 19: pb.ListEntitiesDoneResponse, - 20: pb.SubscribeStatesRequest, - 21: pb.BinarySensorStateResponse, - 22: pb.CoverStateResponse, - 23: pb.FanStateResponse, - 24: pb.LightStateResponse, - 25: pb.SensorStateResponse, - 26: pb.SwitchStateResponse, - 27: pb.TextSensorStateResponse, - 28: pb.SubscribeLogsRequest, - 29: pb.SubscribeLogsResponse, - 30: pb.CoverCommandRequest, - 31: pb.FanCommandRequest, - 32: pb.LightCommandRequest, - 33: pb.SwitchCommandRequest, - 34: pb.SubscribeServiceCallsRequest, - 35: pb.ServiceCallResponse, - 36: pb.GetTimeRequest, - 37: pb.GetTimeResponse, -} - - -def _varuint_to_bytes(value): - if value <= 0x7F: - return bytes([value]) - - ret = bytes() - while value: - temp = value & 0x7F - value >>= 7 - if value: - ret += bytes([temp | 0x80]) - else: - ret += bytes([temp]) - - return ret - - -def _bytes_to_varuint(value): - result = 0 - bitpos = 0 - for val in value: - result |= (val & 0x7F) << bitpos - bitpos += 7 - if (val & 0x80) == 0: - return result - return None - - -# pylint: disable=too-many-instance-attributes,not-callable -class APIClient(threading.Thread): - def __init__(self, address, port, password): - threading.Thread.__init__(self) - self._address = address # type: str - self._port = port # type: int - self._password = password # type: Optional[str] - self._socket = None # type: Optional[socket.socket] - self._socket_open_event = threading.Event() - self._socket_write_lock = threading.Lock() - self._connected = False - self._authenticated = False - self._message_handlers = [] - self._keepalive = 5 - self._ping_timer = None - - self.on_disconnect = None - self.on_connect = None - self.on_login = None - self.auto_reconnect = False - self._running_event = threading.Event() - self._stop_event = threading.Event() - - @property - def stopped(self): - return self._stop_event.is_set() - - def _refresh_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def func(): - self._ping_timer = None - - if self._connected: - try: - self.ping() - except APIConnectionError as err: - self._fatal_error(err) - else: - self._refresh_ping() - - self._ping_timer = threading.Timer(self._keepalive, func) - self._ping_timer.start() - - def _cancel_ping(self): - if self._ping_timer is not None: - self._ping_timer.cancel() - self._ping_timer = None - - def _close_socket(self): - self._cancel_ping() - if self._socket is not None: - self._socket.close() - self._socket = None - self._socket_open_event.clear() - self._connected = False - self._authenticated = False - self._message_handlers = [] - - def stop(self, force=False): - if self.stopped: - raise ValueError - - if self._connected and not force: - try: - self.disconnect() - except APIConnectionError: - pass - self._close_socket() - - self._stop_event.set() - if not force: - self.join() - - def connect(self): - if not self._running_event.wait(0.1): - raise APIConnectionError("You need to call start() first!") - - if self._connected: - self.disconnect(on_disconnect=False) - - try: - ip = resolve_ip_address(self._address) - except EsphomeError as err: - _LOGGER.warning( - "Error resolving IP address of %s. Is it connected to WiFi?", - self._address, - ) - _LOGGER.warning( - "(If this error persists, please set a static IP address: " - "https://esphome.io/components/wifi.html#manual-ips)" - ) - raise APIConnectionError(err) from err - - _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.settimeout(10.0) - self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - try: - self._socket.connect((ip, self._port)) - except OSError as err: - err = APIConnectionError(f"Error connecting to {ip}: {err}") - self._fatal_error(err) - raise err - self._socket.settimeout(0.1) - - self._socket_open_event.set() - - hello = pb.HelloRequest() - hello.client_info = f"ESPHome v{const.__version__}" - try: - resp = self._send_message_await_response(hello, pb.HelloResponse) - except APIConnectionError as err: - self._fatal_error(err) - raise err - _LOGGER.debug( - "Successfully connected to %s ('%s' API=%s.%s)", - self._address, - resp.server_info, - resp.api_version_major, - resp.api_version_minor, - ) - self._connected = True - self._refresh_ping() - if self.on_connect is not None: - self.on_connect() - - def _check_connected(self): - if not self._connected: - err = APIConnectionError("Must be connected!") - self._fatal_error(err) - raise err - - def login(self): - self._check_connected() - if self._authenticated: - raise APIConnectionError("Already logged in!") - - connect = pb.ConnectRequest() - if self._password is not None: - connect.password = self._password - resp = self._send_message_await_response(connect, pb.ConnectResponse) - if resp.invalid_password: - raise APIConnectionError("Invalid password!") - - self._authenticated = True - if self.on_login is not None: - self.on_login() - - def _fatal_error(self, err): - was_connected = self._connected - - self._close_socket() - - if was_connected and self.on_disconnect is not None: - self.on_disconnect(err) - - def _write(self, data): # type: (bytes) -> None - if self._socket is None: - raise APIConnectionError("Socket closed") - - # _LOGGER.debug("Write: %s", format_bytes(data)) - with self._socket_write_lock: - try: - self._socket.sendall(data) - except OSError as err: - err = APIConnectionError(f"Error while writing data: {err}") - self._fatal_error(err) - raise err - - def _send_message(self, msg): - # type: (message.Message) -> None - for message_type, klass in MESSAGE_TYPE_TO_PROTO.items(): - if isinstance(msg, klass): - break - else: - raise ValueError - - encoded = msg.SerializeToString() - _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg))) - req = bytes([0]) - req += _varuint_to_bytes(len(encoded)) - req += _varuint_to_bytes(message_type) - req += encoded - self._write(req) - - def _send_message_await_response_complex( - self, send_msg, do_append, do_stop, timeout=5 - ): - event = threading.Event() - responses = [] - - def on_message(resp): - if do_append(resp): - responses.append(resp) - if do_stop(resp): - event.set() - - self._message_handlers.append(on_message) - self._send_message(send_msg) - ret = event.wait(timeout) - try: - self._message_handlers.remove(on_message) - except ValueError: - pass - if not ret: - raise APIConnectionError("Timeout while waiting for message response!") - return responses - - def _send_message_await_response(self, send_msg, response_type, timeout=5): - def is_response(msg): - return isinstance(msg, response_type) - - return self._send_message_await_response_complex( - send_msg, is_response, is_response, timeout - )[0] - - def device_info(self): - self._check_connected() - return self._send_message_await_response( - pb.DeviceInfoRequest(), pb.DeviceInfoResponse - ) - - def ping(self): - self._check_connected() - return self._send_message_await_response(pb.PingRequest(), pb.PingResponse) - - def disconnect(self, on_disconnect=True): - self._check_connected() - - try: - self._send_message_await_response( - pb.DisconnectRequest(), pb.DisconnectResponse - ) - except APIConnectionError: - pass - self._close_socket() - - if self.on_disconnect is not None and on_disconnect: - self.on_disconnect(None) - - def _check_authenticated(self): - if not self._authenticated: - raise APIConnectionError("Must login first!") - - def subscribe_logs(self, on_log, log_level=7, dump_config=False): - self._check_authenticated() - - def on_msg(msg): - if isinstance(msg, pb.SubscribeLogsResponse): - on_log(msg) - - self._message_handlers.append(on_msg) - req = pb.SubscribeLogsRequest(dump_config=dump_config) - req.level = log_level - self._send_message(req) - - def _recv(self, amount): - ret = bytes() - if amount == 0: - return ret - - while len(ret) < amount: - if self.stopped: - raise APIConnectionError("Stopped!") - if not self._socket_open_event.is_set(): - raise APIConnectionError("No socket!") - try: - val = self._socket.recv(amount - len(ret)) - except AttributeError as err: - raise APIConnectionError("Socket was closed") from err - except socket.timeout: - continue - except OSError as err: - raise APIConnectionError(f"Error while receiving data: {err}") from err - ret += val - return ret - - def _recv_varint(self): - raw = bytes() - while not raw or raw[-1] & 0x80: - raw += self._recv(1) - return _bytes_to_varuint(raw) - - def _run_once(self): - if not self._socket_open_event.wait(0.1): - return - - # Preamble - if self._recv(1)[0] != 0x00: - raise APIConnectionError("Invalid preamble") - - length = self._recv_varint() - msg_type = self._recv_varint() - - raw_msg = self._recv(length) - if msg_type not in MESSAGE_TYPE_TO_PROTO: - _LOGGER.debug("Skipping message type %s", msg_type) - return - - msg = MESSAGE_TYPE_TO_PROTO[msg_type]() - msg.ParseFromString(raw_msg) - _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg))) - for msg_handler in self._message_handlers[:]: - msg_handler(msg) - self._handle_internal_messages(msg) - - def run(self): - self._running_event.set() - while not self.stopped: - try: - self._run_once() - except APIConnectionError as err: - if self.stopped: - break - if self._connected: - _LOGGER.error("Error while reading incoming messages: %s", err) - self._fatal_error(err) - self._running_event.clear() - - def _handle_internal_messages(self, msg): - if isinstance(msg, pb.DisconnectRequest): - self._send_message(pb.DisconnectResponse()) - if self._socket is not None: - self._socket.close() - self._socket = None - self._connected = False - if self.on_disconnect is not None: - self.on_disconnect(None) - elif isinstance(msg, pb.PingRequest): - self._send_message(pb.PingResponse()) - elif isinstance(msg, pb.GetTimeRequest): - resp = pb.GetTimeResponse() - resp.epoch_seconds = int(time.time()) - self._send_message(resp) - - -def run_logs(config, address): - conf = config["api"] - port = conf[CONF_PORT] - password = conf[CONF_PASSWORD] - _LOGGER.info("Starting log output from %s using esphome API", address) - - cli = APIClient(address, port, password) - stopping = False - retry_timer = [] - - has_connects = [] - - def try_connect(err, tries=0): - if stopping: - return - - if err: - _LOGGER.warning("Disconnected from API: %s", err) - - while retry_timer: - retry_timer.pop(0).cancel() - - error = None - try: - cli.connect() - cli.login() - except APIConnectionError as err2: # noqa - error = err2 - - if error is None: - _LOGGER.info("Successfully connected to %s", address) - return - - wait_time = int(min(1.5 ** min(tries, 100), 30)) - if not has_connects: - _LOGGER.warning( - "Initial connection failed. The ESP might not be connected " - "to WiFi yet (%s). Re-Trying in %s seconds", - error, - wait_time, - ) - else: - _LOGGER.warning( - "Couldn't connect to API (%s). Trying to reconnect in %s seconds", - error, - wait_time, - ) - timer = threading.Timer( - wait_time, functools.partial(try_connect, None, tries + 1) - ) - timer.start() - retry_timer.append(timer) - - def on_log(msg): - time_ = datetime.now().time().strftime("[%H:%M:%S]") - text = msg.message - if msg.send_failed: - text = color( - Fore.WHITE, - "(Message skipped because it was too big to fit in " - "TCP buffer - This is only cosmetic)", - ) - safe_print(time_ + text) - - def on_login(): - try: - cli.subscribe_logs(on_log, dump_config=not has_connects) - has_connects.append(True) - except APIConnectionError: - cli.disconnect() - - cli.on_disconnect = try_connect - cli.on_login = on_login - cli.start() - - try: - try_connect(None) - while True: - time.sleep(1) - except KeyboardInterrupt: - stopping = True - cli.stop(True) - while retry_timer: - retry_timer.pop(0).cancel() - return 0 diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py new file mode 100644 index 0000000000..d8192eb88f --- /dev/null +++ b/esphome/components/api/client.py @@ -0,0 +1,73 @@ +import asyncio +import logging +from datetime import datetime +from typing import Optional + +from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel +import zeroconf + +from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__ +from esphome.util import safe_print +from . import CONF_ENCRYPTION + +_LOGGER = logging.getLogger(__name__) + + +async def async_run_logs(config, address): + conf = config["api"] + port: int = conf[CONF_PORT] + password: str = conf[CONF_PASSWORD] + noise_psk: Optional[str] = None + if CONF_ENCRYPTION in conf: + noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] + _LOGGER.info("Starting log output from %s using esphome API", address) + zc = zeroconf.Zeroconf() + cli = APIClient( + asyncio.get_event_loop(), + address, + port, + password, + client_info=f"ESPHome Logs {__version__}", + noise_psk=noise_psk, + ) + first_connect = True + + def on_log(msg): + time_ = datetime.now().time().strftime("[%H:%M:%S]") + text = msg.message.decode("utf8", "backslashreplace") + safe_print(time_ + text) + + async def on_connect(): + nonlocal first_connect + try: + await cli.subscribe_logs( + on_log, + log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE, + dump_config=first_connect, + ) + first_connect = False + except APIConnectionError: + cli.disconnect() + + async def on_disconnect(): + _LOGGER.warning("Disconnected from API") + + zc = zeroconf.Zeroconf() + reconnect = ReconnectLogic( + client=cli, + on_connect=on_connect, + on_disconnect=on_disconnect, + zeroconf_instance=zc, + ) + await reconnect.start() + + try: + while True: + await asyncio.sleep(60) + except KeyboardInterrupt: + await reconnect.stop() + zc.close() + + +def run_logs(config, address): + asyncio.run(async_run_logs(config, address)) diff --git a/requirements.txt b/requirements.txt index 2d354d5f04..18752e16a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,11 @@ PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 -protobuf==3.17.3 tzlocal==2.1 pytz==2021.1 pyserial==3.5 -ifaddr==0.1.7 platformio==5.2.0 esptool==3.1 click==7.1.2 esphome-dashboard==20210908.0 +aioesphomeapi==9.0.0 From c5d26a5b4acdb61cf36de228f0e5f480aac42ab3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 10:26:49 +0200 Subject: [PATCH 226/285] Bump click from 7.1.2 to 8.0.1 (#1824) Bumps [click](https://github.com/pallets/click) from 7.1.2 to 8.0.1. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/7.1.2...8.0.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 18752e16a3..5ab68beb2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ pytz==2021.1 pyserial==3.5 platformio==5.2.0 esptool==3.1 -click==7.1.2 +click==8.0.1 esphome-dashboard==20210908.0 aioesphomeapi==9.0.0 From 5f61897becd2ec2c5d495431df6ffc5c519eeb45 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 10:35:37 +0200 Subject: [PATCH 227/285] Add stale/lock bots (#2299) --- .github/workflows/lock.yml | 21 +++++++++++++++++++++ .github/workflows/stale.yml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/lock.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 0000000000..aedb9dd9b3 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,21 @@ +name: Lock + +on: + schedule: + - cron: "0 * * * *" + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + pr-lock-inactive-days: "1" + pr-lock-reason: "" + process-only: prs diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..f26dd32929 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,30 @@ +name: Stale + +on: + schedule: + - cron: "0 * * * *" + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + repo-token: ${{ github.token }} + days-before-pr-stale: 90 + days-before-pr-close: 7 + days-before-issue-stale: -1 + days-before-issue-close: -1 + remove-stale-when-updated: true + stale-pr-label: "stale" + exempt-pr-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request recently. This + pull request has been automatically marked as stale because of that + and will be closed if no further activity occurs within 7 days. + Thank you for your contributions. From dd3f2f6c7ebca5eb1bc2561d651b989154596ef9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 14 Sep 2021 11:53:49 +0200 Subject: [PATCH 228/285] Fix api noise explicit reject (#2297) --- esphome/components/api/api_frame_helper.cpp | 41 ++++++++++++++++----- esphome/components/api/api_frame_helper.h | 2 + 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c064c7278f..e68831e594 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -61,11 +61,15 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SETUP_FAILED"; } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) { return "HANDSHAKESTATE_SPLIT_FAILED"; + } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { + return "BAD_HANDSHAKE_ERROR_BYTE"; } return "UNKNOWN"; } #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__) +// uncomment to log raw packets +//#define HELPER_LOG_PACKETS #ifdef USE_API_NOISE static const char *const PROLOGUE_INIT = "NoiseAPIInit"; @@ -236,7 +240,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -265,6 +271,14 @@ APIError APINoiseFrameHelper::state_action_() { // waiting for client hello ParsedFrame frame; aerr = try_read_frame_(&frame); + if (aerr == APIError::BAD_INDICATOR) { + send_explicit_handshake_reject_("Bad indicator byte"); + return aerr; + } + if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) { + send_explicit_handshake_reject_("Bad handshake packet len"); + return aerr; + } if (aerr != APIError::OK) return aerr; // ignore contents, may be used in future for flags @@ -308,11 +322,11 @@ APIError APINoiseFrameHelper::state_action_() { if (frame.msg.empty()) { send_explicit_handshake_reject_("Empty handshake message"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } else if (frame.msg[0] != 0x00) { HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]); send_explicit_handshake_reject_("Bad handshake error byte"); - return APIError::BAD_HANDSHAKE_PACKET_LEN; + return APIError::BAD_HANDSHAKE_ERROR_BYTE; } NoiseBuffer mbuf; @@ -320,7 +334,6 @@ APIError APINoiseFrameHelper::state_action_() { noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1); err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); if (err != 0) { - // TODO: explicit rejection state_ = State::FAILED; HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str()); if (err == NOISE_ERROR_MAC_FAILURE) { @@ -368,12 +381,16 @@ APIError APINoiseFrameHelper::state_action_() { } void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) { std::vector data; - data.reserve(reason.size() + 1); + data.resize(reason.length() + 1); data[0] = 0x01; // failure - for (size_t i = 0; i < reason.size(); i++) { + for (size_t i = 0; i < reason.length(); i++) { data[i + 1] = (uint8_t) reason[i]; } + // temporarily remove failed state + auto orig_state = state_; + state_ = State::EXPLICIT_REJECT; write_frame_(data.data(), data.size()); + state_ = orig_state; } APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { @@ -516,7 +533,9 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -799,7 +818,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { } // uncomment for even more debugging - // ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); +#endif frame->msg = std::move(rx_buf_); // consume msg rx_buf_ = {}; @@ -882,7 +903,9 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { APIError aerr; // uncomment for even more debugging - // ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#ifdef HELPER_LOG_PACKETS + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); +#endif if (!tx_buf_.empty()) { // try to empty tx_buf_ first diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a8974cd25f..a9a653cf4f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -51,6 +51,7 @@ enum class APIError : int { OUT_OF_MEMORY = 1018, HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, + BAD_HANDSHAKE_ERROR_BYTE = 1021, }; const char *api_error_to_str(APIError err); @@ -125,6 +126,7 @@ class APINoiseFrameHelper : public APIFrameHelper { DATA = 5, CLOSED = 6, FAILED = 7, + EXPLICIT_REJECT = 8, } state_ = State::INITIALIZE; }; #endif // USE_API_NOISE From d437cc915c9fc10644299b3c189f1aedd826ceb3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:40:45 +1200 Subject: [PATCH 229/285] Allow simple hostname for sntp servers (#2300) --- esphome/components/sntp/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 5475dc0a1f..b1362f5421 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -16,7 +16,7 @@ CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SNTPComponent), cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( - cv.ensure_list(cv.domain), cv.Length(min=1, max=3) + cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3) ), } ).extend(cv.COMPONENT_SCHEMA) From 4cc2817fcd42d0d208a00561dcca0dc9b7377a4d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:59:15 +1200 Subject: [PATCH 230/285] Fix binary strobe (#2301) --- esphome/components/light/base_light_effects.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 7826b2eecb..5ab9f66ce4 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -156,7 +156,7 @@ class StrobeLightEffect : public LightEffect { if (!color.is_on()) { // Don't turn the light off, otherwise the light effect will be stopped - call.set_brightness_if_supported(0.0f); + call.set_brightness(0.0f); call.set_state(true); } call.set_publish(false); From 716039e452b435ea15fcfc5b94c59cb45e277b99 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 14 Sep 2021 14:27:35 +0200 Subject: [PATCH 231/285] Use standard version of make_unique when available (#2292) --- .clang-tidy | 5 ++++- esphome/components/nfc/ndef_message.cpp | 4 ++-- esphome/components/nfc/nfc_tag.h | 4 ++-- esphome/components/pn532/pn532.cpp | 10 ++++++---- esphome/components/pn532/pn532_mifare_classic.cpp | 10 ++++++---- esphome/components/pn532/pn532_mifare_ultralight.cpp | 12 +++++++----- esphome/components/socket/bsd_sockets_impl.cpp | 3 ++- esphome/core/helpers.h | 7 ++++++- 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 890eb18608..98a1568e5a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -70,7 +70,6 @@ Checks: >- -modernize-use-default-member-init, -modernize-use-equals-default, -modernize-use-trailing-return-type, - -modernize-make-unique, -modernize-use-nodiscard, -mpi-*, -objc-*, @@ -114,6 +113,10 @@ CheckOptions: value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: 'esphome/core/helpers.h' - key: readability-identifier-naming.LocalVariableCase value: 'lower_case' - key: readability-identifier-naming.ClassCase diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index 147392940c..b1554f41ae 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -83,11 +83,11 @@ bool NdefMessage::add_text_record(const std::string &text) { return this->add_te bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { std::string payload = to_string(text.length()) + encoding + text; - return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "T", payload)}); + return this->add_record(make_unique(TNF_WELL_KNOWN, "T", payload)); } bool NdefMessage::add_uri_record(const std::string &uri) { - return this->add_record(std::unique_ptr{new NdefRecord(TNF_WELL_KNOWN, "U", uri)}); + return this->add_record(make_unique(TNF_WELL_KNOWN, "U", uri)); } std::vector NdefMessage::encode() { diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index 2c8b0a5f21..ab6ca650e4 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -31,13 +31,13 @@ class NfcTag { NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { this->uid_ = uid; this->tag_type_ = tag_type; - this->ndef_message_ = std::unique_ptr(new NdefMessage(ndef_data)); + this->ndef_message_ = make_unique(ndef_data); }; NfcTag(const NfcTag &rhs) { uid_ = rhs.uid_; tag_type_ = rhs.tag_type_; if (rhs.ndef_message_ != nullptr) - ndef_message_ = std::unique_ptr(new NdefMessage(*rhs.ndef_message_)); + ndef_message_ = make_unique(*rhs.ndef_message_); } std::vector &get_uid() { return this->uid_; }; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index c28ce8d503..1c4160539a 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -1,4 +1,6 @@ #include "pn532.h" + +#include #include "esphome/core/log.h" // Based on: @@ -104,7 +106,7 @@ void PN532::loop() { if (!success) { // Something failed if (!this->current_uid_.empty()) { - auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -117,7 +119,7 @@ void PN532::loop() { if (num_targets != 1) { // no tags found or too many if (!this->current_uid_.empty()) { - auto tag = std::unique_ptr{new nfc::NfcTag(this->current_uid_)}; + auto tag = make_unique(this->current_uid_); for (auto *trigger : this->triggers_ontagremoved_) trigger->process(tag); } @@ -281,9 +283,9 @@ std::unique_ptr PN532::read_tag_(std::vector &uid) { return this->read_mifare_ultralight_tag_(uid); } else if (type == nfc::TAG_TYPE_UNKNOWN) { ESP_LOGV(TAG, "Cannot determine tag type"); - return std::unique_ptr{new nfc::NfcTag(uid)}; + return make_unique(uid); } else { - return std::unique_ptr{new nfc::NfcTag(uid)}; + return make_unique(uid); } } diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index f4bd11d49f..81d135d8e6 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -1,3 +1,5 @@ +#include + #include "pn532.h" #include "esphome/core/log.h" @@ -15,15 +17,15 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector data; if (this->read_mifare_classic_block_(current_block, data)) { if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::ERROR)}; + return make_unique(uid, nfc::ERROR); } } else { ESP_LOGE(TAG, "Failed to read block %d", current_block); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; + return make_unique(uid, nfc::MIFARE_CLASSIC); } } else { ESP_LOGV(TAG, "Tag is not NDEF formatted"); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC)}; + return make_unique(uid, nfc::MIFARE_CLASSIC); } uint32_t index = 0; @@ -51,7 +53,7 @@ std::unique_ptr PN532::read_mifare_classic_tag_(std::vector{new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer)}; + return make_unique(uid, nfc::MIFARE_CLASSIC, buffer); } bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 24e97ab95c..1b91ae919e 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -1,3 +1,5 @@ +#include + #include "pn532.h" #include "esphome/core/log.h" @@ -9,25 +11,25 @@ static const char *const TAG = "pn532.mifare_ultralight"; std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector &uid) { if (!this->is_mifare_ultralight_formatted_()) { ESP_LOGD(TAG, "Not NDEF formatted"); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } uint8_t message_length; uint8_t message_start_index; if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); if (message_length == 0) { - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } std::vector data; for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { std::vector page_data; if (!this->read_mifare_ultralight_page_(page, page_data)) { ESP_LOGE(TAG, "Error reading page %d", page); - return std::unique_ptr{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); } data.insert(data.end(), page_data.begin(), page_data.end()); @@ -38,7 +40,7 @@ std::unique_ptr PN532::read_mifare_ultralight_tag_(std::vector{new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data)}; + return make_unique(uid, nfc::NFC_FORUM_TYPE_2, data); } bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 1ad520a099..6fb00ce22d 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -1,5 +1,6 @@ #include "socket.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #ifdef USE_SOCKET_IMPL_BSD_SOCKETS @@ -39,7 +40,7 @@ class BSDSocketImpl : public Socket { int fd = ::accept(fd_, addr, addrlen); if (fd == -1) return {}; - return std::unique_ptr{new BSDSocketImpl(fd)}; + return make_unique(fd); } int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } int close() override { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 86c70088d4..6c0ee8399e 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -88,10 +88,15 @@ template T clamp(T val, T min, T max); */ float lerp(float completion, float start, float end); -/// std::make_unique +// Not all platforms we support target C++14 yet, so we can't unconditionally use std::make_unique. Provide our own +// implementation if needed, and otherwise pull std::make_unique into scope so that we have a uniform API. +#if __cplusplus >= 201402L +using std::make_unique; +#else template std::unique_ptr make_unique(Args &&...args) { return std::unique_ptr(new T(std::forward(args)...)); } +#endif /// Return a random 32 bit unsigned integer. uint32_t random_uint32(); From 5a90b83f6385f4d8b154bdb3a0c396d37c3385e2 Mon Sep 17 00:00:00 2001 From: jsuanet <75206491+jsuanet@users.noreply.github.com> Date: Tue, 14 Sep 2021 23:22:45 +0200 Subject: [PATCH 232/285] Fix unit of measurement fields for DSMR power consumed/delivered fields (#2304) Co-authored-by: Jos Suanet --- esphome/components/dsmr/sensor.py | 134 +++++++++++++++++++++++------- esphome/const.py | 2 + 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 2c05651d67..9d531293e9 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -13,9 +13,13 @@ from esphome.const import ( STATE_CLASS_NONE, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, + UNIT_CUBIC_METER, UNIT_EMPTY, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_VOLT, - UNIT_WATT, ) from . import Dsmr, CONF_DSMR_ID @@ -26,40 +30,80 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - "kWh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_imported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( - "kvarh", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + ICON_EMPTY, + 3, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( - "kvar", ICON_EMPTY, 3, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -77,13 +121,13 @@ CONFIG_SCHEMA = cv.Schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT + UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE @@ -101,40 +145,64 @@ CONFIG_SCHEMA = cv.Schema( UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + UNIT_KILOVOLT_AMPS_REACTIVE, + ICON_EMPTY, + 3, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE @@ -146,10 +214,18 @@ CONFIG_SCHEMA = cv.Schema( UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE ), cv.Optional("gas_delivered"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - "m³", ICON_EMPTY, 3, DEVICE_CLASS_GAS, STATE_CLASS_TOTAL_INCREASING + UNIT_CUBIC_METER, + ICON_EMPTY, + 3, + DEVICE_CLASS_GAS, + STATE_CLASS_TOTAL_INCREASING, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/const.py b/esphome/const.py index f342609538..92d18cde3a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -786,7 +786,9 @@ UNIT_KELVIN = "K" UNIT_KILOGRAM = "kg" UNIT_KILOMETER = "km" UNIT_KILOMETER_PER_HOUR = "km/h" +UNIT_KILOVOLT_AMPS_REACTIVE = "kVAr" UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVArh" +UNIT_KILOWATT = "kW" UNIT_KILOWATT_HOURS = "kWh" UNIT_LUX = "lx" UNIT_METER = "m" From 103ba4c696361d1f34a9b071c7175e5be63f205d Mon Sep 17 00:00:00 2001 From: JonasEr <8407728+JonasEr@users.noreply.github.com> Date: Wed, 15 Sep 2021 01:06:43 +0300 Subject: [PATCH 233/285] Bug fix of NFC message & records becoming inaccessible in on_tag lambdas (#2309) --- esphome/components/nfc/ndef_message.h | 4 ++-- esphome/components/nfc/nfc_tag.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h index d2ffa9582e..20140e8c1a 100644 --- a/esphome/components/nfc/ndef_message.h +++ b/esphome/components/nfc/ndef_message.h @@ -22,7 +22,7 @@ class NdefMessage { } } - const std::vector> &get_records() { return this->records_; }; + const std::vector> &get_records() { return this->records_; }; bool add_record(std::unique_ptr record); bool add_text_record(const std::string &text); @@ -32,7 +32,7 @@ class NdefMessage { std::vector encode(); protected: - std::vector> records_; + std::vector> records_; }; } // namespace nfc diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h index ab6ca650e4..2dfc431428 100644 --- a/esphome/components/nfc/nfc_tag.h +++ b/esphome/components/nfc/nfc_tag.h @@ -43,13 +43,13 @@ class NfcTag { std::vector &get_uid() { return this->uid_; }; const std::string &get_tag_type() { return this->tag_type_; }; bool has_ndef_message() { return this->ndef_message_ != nullptr; }; - const std::unique_ptr &get_ndef_message() { return this->ndef_message_; }; + const std::shared_ptr &get_ndef_message() { return this->ndef_message_; }; void set_ndef_message(std::unique_ptr ndef_message) { this->ndef_message_ = std::move(ndef_message); }; protected: std::vector uid_; std::string tag_type_; - std::unique_ptr ndef_message_; + std::shared_ptr ndef_message_; }; } // namespace nfc From de33cbd7e7c37670d5a3c4262af024ea222b986e Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 14 Sep 2021 22:14:49 -0300 Subject: [PATCH 234/285] Dsmr updates (#2157) * add option to use check_crc * ignore newline before ( in parsing * add gas delivered text for raw sensor * fix compile issue when not listing any sensor * make gas_mbus_id configurable * update dsmr lib for clang --- esphome/components/dsmr/__init__.py | 10 ++++++++-- esphome/components/dsmr/dsmr.cpp | 10 ++++++++-- esphome/components/dsmr/dsmr.h | 3 ++- esphome/components/dsmr/sensor.py | 5 ++++- esphome/components/dsmr/text_sensor.py | 13 ++++++++++--- platformio.ini | 2 +- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index df11a14ee8..1f1a2f980e 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -13,6 +13,8 @@ AUTO_LOAD = ["sensor", "text_sensor"] CONF_DSMR_ID = "dsmr_id" CONF_DECRYPTION_KEY = "decryption_key" +CONF_CRC_CHECK = "crc_check" +CONF_GAS_MBUS_ID = "gas_mbus_id" # Hack to prevent compile error due to ambiguity with lib namespace dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") @@ -41,19 +43,23 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Dsmr), cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, + cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, } ).extend(uart.UART_DEVICE_SCHEMA) async def to_code(config): uart_component = await cg.get_variable(config[CONF_UART_ID]) - var = cg.new_Pvariable(config[CONF_ID], uart_component) + var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) if CONF_DECRYPTION_KEY in config: cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) await cg.register_component(var, config) + cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) + # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.3") + cg.add_library("glmnet/Dsmr", "0.5") # Crypto cg.add_library("rweather/Crypto", "0.2.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 39f05a28d1..7c1b406d42 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -37,6 +37,12 @@ void Dsmr::receive_telegram_() { return; } + // Some v2.2 or v3 meters will send a new value which starts with '(' + // in a new line while the value belongs to the previous ObisId. For + // proper parsing remove these new line characters + while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) + telegram_len_--; + telegram_[telegram_len_] = c; telegram_len_++; if (c == '!') { // footer: exclamation mark @@ -130,8 +136,8 @@ bool Dsmr::parse_telegram() { MyData data; ESP_LOGV(TAG, "Trying to parse"); ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, - false); // Parse telegram according to data definition. Ignore unknown values. + ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 984f2596db..dcb8f5f73d 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -48,7 +48,7 @@ using MyData = ::dsmr::ParsedData decryption_key_{}; + bool crc_check_; }; } // namespace dsmr } // namespace esphome diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 9d531293e9..761009c766 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -244,4 +244,7 @@ async def to_code(config): cg.add(getattr(hub, f"set_{key}")(s)) sensors.append(f"F({key})") - cg.add_define("DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))) + if sensors: + cg.add_define( + "DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors)) + ) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 821b07dc6b..339eea711f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -71,6 +71,11 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), } ), + cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -89,6 +94,8 @@ async def to_code(config): cg.add(getattr(hub, f"set_{key}")(var)) text_sensors.append(f"F({key})") - cg.add_define( - "DSMR_TEXT_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(text_sensors)) - ) + if text_sensors: + cg.add_define( + "DSMR_TEXT_SENSOR_LIST(F, sep)", + cg.RawExpression(" sep ".join(text_sensors)), + ) diff --git a/platformio.ini b/platformio.ini index 3b1241f282..174832b067 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,7 +34,7 @@ lib_deps = 1655@1.0.2 ; TinyGPSPlus (has name conflict) 6865@1.0.0 ; TM1651 Battery Display 6306@1.0.3 ; HM3301 - glmnet/Dsmr@0.3 ; used by dsmr + glmnet/Dsmr@0.5 ; used by dsmr rweather/Crypto@0.2.0 ; used by dsmr esphome/noise-c@0.1.1 ; used by api dudanov/MideaUART@1.1.0 ; used by midea From b276ac0588d665351ed621ddfa0ad8f5de945a77 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 16:39:13 +1200 Subject: [PATCH 235/285] Simple time.sleep in place of threading wait due to upgraded zeroconf (#2307) --- esphome/zeroconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index 443ed6a33a..e6853531f2 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -49,7 +49,7 @@ class HostResolver(RecordUpdateListener): next_ = now + delay delay *= 2 - zc.wait(min(next_, last) - now) + time.sleep(min(next_, last) - now) now = time.time() finally: zc.remove_listener(self) From 19014331d8225157cfa3390e36641c1c876b5db5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 15 Sep 2021 08:48:27 +0200 Subject: [PATCH 236/285] Fix aioesphomeapi API logger with explicit api.port in the YAML. (#2310) --- esphome/components/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index d8192eb88f..4a3944d33e 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) async def async_run_logs(config, address): conf = config["api"] - port: int = conf[CONF_PORT] + port: int = int(conf[CONF_PORT]) password: str = conf[CONF_PASSWORD] noise_psk: Optional[str] = None if CONF_ENCRYPTION in conf: From 0ea77de98cfa8af51fcf2cef5f7745a7bdad7b94 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Sep 2021 19:00:51 +1200 Subject: [PATCH 237/285] Start a wifi scan after saving station details (#2315) --- esphome/components/wifi/wifi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index c5ba9b01af..0a1e347e8f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -244,6 +244,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa sta.set_ssid(ssid); sta.set_password(password); this->set_sta(sta); + + this->start_scanning(); } void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { From 2db8c42e1daeb47b4ee161446bdac38933e60078 Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Wed, 15 Sep 2021 10:23:35 +0200 Subject: [PATCH 238/285] Support direct relay state feedback for tuya climate component (#1668) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tuya/climate/__init__.py | 59 ++++++++++++++----- .../components/tuya/climate/tuya_climate.cpp | 47 +++++++++++++++ .../components/tuya/climate/tuya_climate.h | 7 +++ 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 06c80964ee..471a8146e1 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -1,3 +1,4 @@ +from esphome import pins from esphome.components import climate import esphome.config_validation as cv import esphome.codegen as cg @@ -15,6 +16,8 @@ CODEOWNERS = ["@jesserockz"] CONF_ACTIVE_STATE_DATAPOINT = "active_state_datapoint" CONF_ACTIVE_STATE_HEATING_VALUE = "active_state_heating_value" CONF_ACTIVE_STATE_COOLING_VALUE = "active_state_cooling_value" +CONF_HEATING_STATE_PIN = "heating_state_pin" +CONF_COOLING_STATE_PIN = "cooling_state_pin" CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" @@ -69,14 +72,21 @@ def validate_temperature_multipliers(value): def validate_active_state_values(value): if CONF_ACTIVE_STATE_DATAPOINT not in value: - return value - if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: - raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" + if CONF_ACTIVE_STATE_COOLING_VALUE in value: + raise cv.Invalid( + ( + f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " + f"{CONF_ACTIVE_STATE_COOLING_VALUE}" + ) + ) + else: + if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: + raise cv.Invalid( + ( + f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " + f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" + ) ) - ) return value @@ -91,6 +101,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ACTIVE_STATE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_ACTIVE_STATE_HEATING_VALUE, default=1): cv.uint8_t, cv.Optional(CONF_ACTIVE_STATE_COOLING_VALUE): cv.uint8_t, + cv.Optional(CONF_HEATING_STATE_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_COOLING_STATE_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, @@ -101,6 +113,8 @@ CONFIG_SCHEMA = cv.All( cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, validate_active_state_values, + cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), + cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), ) @@ -118,14 +132,29 @@ async def to_code(config): cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) if CONF_ACTIVE_STATE_DATAPOINT in config: cg.add(var.set_active_state_id(config[CONF_ACTIVE_STATE_DATAPOINT])) - if CONF_ACTIVE_STATE_HEATING_VALUE in config: - cg.add( - var.set_active_state_heating_value(config[CONF_ACTIVE_STATE_HEATING_VALUE]) - ) - if CONF_ACTIVE_STATE_COOLING_VALUE in config: - cg.add( - var.set_active_state_cooling_value(config[CONF_ACTIVE_STATE_COOLING_VALUE]) - ) + if CONF_ACTIVE_STATE_HEATING_VALUE in config: + cg.add( + var.set_active_state_heating_value( + config[CONF_ACTIVE_STATE_HEATING_VALUE] + ) + ) + if CONF_ACTIVE_STATE_COOLING_VALUE in config: + cg.add( + var.set_active_state_cooling_value( + config[CONF_ACTIVE_STATE_COOLING_VALUE] + ) + ) + else: + if CONF_HEATING_STATE_PIN in config: + heating_state_pin = await cg.gpio_pin_expression( + config[CONF_HEATING_STATE_PIN] + ) + cg.add(var.set_heating_state_pin(heating_state_pin)) + if CONF_COOLING_STATE_PIN in config: + cooling_state_pin = await cg.gpio_pin_expression( + config[CONF_COOLING_STATE_PIN] + ) + cg.add(var.set_cooling_state_pin(cooling_state_pin)) if CONF_TARGET_TEMPERATURE_DATAPOINT in config: cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index a4ce06476d..ae4424e438 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -31,6 +31,15 @@ void TuyaClimate::setup() { this->compute_state_(); this->publish_state(); }); + } else { + if (this->heating_state_pin_ != nullptr) { + this->heating_state_pin_->setup(); + this->heating_state_ = this->heating_state_pin_->digital_read(); + } + if (this->cooling_state_pin_ != nullptr) { + this->cooling_state_pin_->setup(); + this->cooling_state_ = this->cooling_state_pin_->digital_read(); + } } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) { @@ -50,6 +59,34 @@ void TuyaClimate::setup() { } } +void TuyaClimate::loop() { + if (this->active_state_id_.has_value()) + return; + + bool state_changed = false; + if (this->heating_state_pin_ != nullptr) { + bool heating_state = this->heating_state_pin_->digital_read(); + if (heating_state != this->heating_state_) { + ESP_LOGV(TAG, "Heating state pin changed to: %s", ONOFF(heating_state)); + this->heating_state_ = heating_state; + state_changed = true; + } + } + if (this->cooling_state_pin_ != nullptr) { + bool cooling_state = this->cooling_state_pin_->digital_read(); + if (cooling_state != this->cooling_state_) { + ESP_LOGV(TAG, "Cooling state pin changed to: %s", ONOFF(cooling_state)); + this->cooling_state_ = cooling_state; + state_changed = true; + } + } + + if (state_changed) { + this->compute_state_(); + this->publish_state(); + } +} + void TuyaClimate::control(const climate::ClimateCall &call) { if (call.get_mode().has_value()) { const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF; @@ -86,6 +123,8 @@ void TuyaClimate::dump_config() { ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_); if (this->current_temperature_id_.has_value()) ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); + LOG_PIN(" Heating State Pin: ", this->heating_state_pin_); + LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_); } void TuyaClimate::compute_state_() { @@ -102,6 +141,7 @@ void TuyaClimate::compute_state_() { climate::ClimateAction target_action = climate::CLIMATE_ACTION_IDLE; if (this->active_state_id_.has_value()) { + // Use state from MCU datapoint if (this->supports_heat_ && this->active_state_heating_value_.has_value() && this->active_state_ == this->active_state_heating_value_) { target_action = climate::CLIMATE_ACTION_HEATING; @@ -109,6 +149,13 @@ void TuyaClimate::compute_state_() { this->active_state_ == this->active_state_cooling_value_) { target_action = climate::CLIMATE_ACTION_COOLING; } + } else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { + // Use state from input pins + if (this->heating_state_) { + target_action = climate::CLIMATE_ACTION_HEATING; + } else if (this->cooling_state_) { + target_action = climate::CLIMATE_ACTION_COOLING; + } } else { // Fallback to active state calc based on temp and hysteresis const float temp_diff = this->target_temperature - this->current_temperature; diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index f015bc337c..f1a0c13a77 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -10,6 +10,7 @@ namespace tuya { class TuyaClimate : public climate::Climate, public Component { public: void setup() override; + void loop() override; void dump_config() override; void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } @@ -17,6 +18,8 @@ class TuyaClimate : public climate::Climate, public Component { void set_active_state_id(uint8_t state_id) { this->active_state_id_ = state_id; } void set_active_state_heating_value(uint8_t value) { this->active_state_heating_value_ = value; } void set_active_state_cooling_value(uint8_t value) { this->active_state_cooling_value_ = value; } + void set_heating_state_pin(GPIOPin *pin) { this->heating_state_pin_ = pin; } + void set_cooling_state_pin(GPIOPin *pin) { this->cooling_state_pin_ = pin; } void set_target_temperature_id(uint8_t target_temperature_id) { this->target_temperature_id_ = target_temperature_id; } @@ -51,12 +54,16 @@ class TuyaClimate : public climate::Climate, public Component { optional active_state_id_{}; optional active_state_heating_value_{}; optional active_state_cooling_value_{}; + GPIOPin *heating_state_pin_{nullptr}; + GPIOPin *cooling_state_pin_{nullptr}; optional target_temperature_id_{}; optional current_temperature_id_{}; float current_temperature_multiplier_{1.0f}; float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; uint8_t active_state_; + bool heating_state_{false}; + bool cooling_state_{false}; }; } // namespace tuya From d281e59f3a317019dbf41d742458c4e42f3b3800 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 15 Sep 2021 08:40:52 -0300 Subject: [PATCH 239/285] ac_dimmer increase gate time for robotdyn (#1708) * ac_dimmer increate gate time for robotdyn * add explanation on longer gate enable time --- esphome/components/ac_dimmer/ac_dimmer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 9a71f0e224..e86703bfe1 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -17,7 +17,10 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no /// Time in microseconds the gate should be held high /// 10µs should be long enough for most triacs /// For reference: BT136 datasheet says 2µs nominal (page 7) -static const uint32_t GATE_ENABLE_TIME = 10; +/// However other factors like gate driver propagation time +/// are also considered and a really low value is not important +/// See also: https://github.com/esphome/issues/issues/1632 +static const uint32_t GATE_ENABLE_TIME = 50; /// Function called from timer interrupt /// Input is current time in microseconds (micros()) From 607ddaa632790b75d6a26f2bf5ceb17855ca51f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:02:28 +0200 Subject: [PATCH 240/285] Bump aioesphomeapi from 9.0.0 to 9.1.0 (#2306) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5ab68beb2e..64ffd366e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 -aioesphomeapi==9.0.0 +aioesphomeapi==9.1.0 From 6366ff6421d7cf807df66bc508891df33137786c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 18:02:41 +0200 Subject: [PATCH 241/285] Bump black from 21.8b0 to 21.9b0 (#2305) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 59085b33e2..bb735193f0 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.10.2 flake8==3.9.2 -black==21.8b0 +black==21.9b0 pexpect==4.8.0 pre-commit From c6dc8a11e24f9332bc6557c8733fd12d45ed3dc2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:01:31 +0200 Subject: [PATCH 242/285] Add namespace to all PlatformIO library references (#2296) * Remove unnecessary duplication in platformio.ini * Add namespace to all platformio library references * Add cmake-build-* to gitignore They're generated by the CLion add-on for each PlatformIO environment. Listing them all separately seems nonsensical. --- .gitignore | 5 +-- esphome/components/async_tcp/__init__.py | 2 +- esphome/components/fastled_base/__init__.py | 2 +- esphome/components/gps/__init__.py | 2 +- esphome/components/hm3301/sensor.py | 2 +- esphome/components/json/__init__.py | 2 +- esphome/components/mqtt/__init__.py | 2 +- esphome/components/neopixelbus/light.py | 2 +- esphome/components/tm1651/__init__.py | 2 +- platformio.ini | 46 +++++++++------------ 10 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 92d92e4b3b..cd7822bded 100644 --- a/.gitignore +++ b/.gitignore @@ -102,10 +102,7 @@ CMakeLists.txt .idea/**/dynamic.xml # CMake -cmake-build-debug/ -cmake-build-livingroom8266/ -cmake-build-livingroom32/ -cmake-build-release/ +cmake-build-*/ CMakeCache.txt CMakeFiles diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 8938dc4671..22d4bb1fcb 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -12,4 +12,4 @@ async def to_code(config): cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") elif CORE.is_esp8266: # https://github.com/OttoWinter/ESPAsyncTCP - cg.add_library("ESPAsyncTCP-esphome", "1.2.3") + cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index f2d0bb1f38..62de036e62 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -44,5 +44,5 @@ async def new_fastled_light(config): # https://github.com/FastLED/FastLED/blob/master/library.json # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless: # https://github.com/esphome/issues/issues/1375 - cg.add_library("FastLED", "3.3.2") + cg.add_library("fastled/FastLED", "3.3.2") return var diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 0d5c3f3f3d..9d323e6e4d 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -99,4 +99,4 @@ async def to_code(config): cg.add(var.set_satellites_sensor(sens)) # https://platformio.org/lib/show/1655/TinyGPSPlus - cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict + cg.add_library("mikalhart/TinyGPSPlus", "1.0.2") diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 7cd81fec1d..2c3c2f7221 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -110,4 +110,4 @@ async def to_code(config): cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 - cg.add_library("6306", "1.0.3") + cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3") diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 7bdef6ea0e..9879754d9e 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -7,6 +7,6 @@ json_ns = cg.esphome_ns.namespace("json") @coroutine_with_priority(1.0) async def to_code(config): - cg.add_library("ArduinoJson-esphomelib", "5.13.3") + cg.add_library("ottowinter/ArduinoJson-esphomelib", "5.13.3") cg.add_define("USE_JSON") cg.add_global(json_ns.using) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 56ea9027af..19f78641df 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -214,7 +214,7 @@ async def to_code(config): await cg.register_component(var, config) # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("AsyncMqttClient-esphome", "0.8.4") + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.4") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 575f69b731..5eee5210f9 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -205,4 +205,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("NeoPixelBus", "2.6.7") + cg.add_library("makuna/NeoPixelBus", "2.6.7") diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index f67a9f4512..d06c1bedde 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -50,7 +50,7 @@ async def to_code(config): cg.add(var.set_dio_pin(dio_pin)) # https://platformio.org/lib/show/6865/TM1651 - cg.add_library("6865", "1.0.1") + cg.add_library("freekode/TM1651", "1.0.1") BINARY_OUTPUT_ACTION_SCHEMA = maybe_simple_id( diff --git a/platformio.ini b/platformio.ini index 174832b067..89e9ce9374 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266 +default_envs = esp8266, esp32 src_dir = . include_dir = @@ -26,19 +26,18 @@ build_flags = [common] lib_deps = - AsyncMqttClient-esphome@0.8.4 - ArduinoJson-esphomelib@5.13.3 - esphome/ESPAsyncWebServer-esphome@1.3.0 - FastLED@3.3.2 - NeoPixelBus@2.6.7 - 1655@1.0.2 ; TinyGPSPlus (has name conflict) - 6865@1.0.0 ; TM1651 Battery Display - 6306@1.0.3 ; HM3301 - glmnet/Dsmr@0.5 ; used by dsmr - rweather/Crypto@0.2.0 ; used by dsmr - esphome/noise-c@0.1.1 ; used by api - dudanov/MideaUART@1.1.0 ; used by midea - + ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/ArduinoJson-esphomelib@5.13.3 ; json + esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + fastled/FastLED@3.3.2 ; fastled_base + makuna/NeoPixelBus@2.6.7 ; neopixelbus + mikalhart/TinyGPSPlus@1.0.2 ; gps + freekode/TM1651@1.0.1 ; tm1651 + seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 + glmnet/Dsmr@0.5 ; dsmr + rweather/Crypto@0.2.0 ; dsmr + esphome/noise-c@0.1.1 ; api + dudanov/MideaUART@1.1.0 ; midea build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -47,30 +46,25 @@ src_filter = +<.temp/all-include.cpp> [common:esp8266] +extends = common platform = platformio/espressif8266@3.1.0 framework = arduino board = nodemcuv2 lib_deps = ${common.lib_deps} - ESP8266WiFi - ESPAsyncTCP-esphome@1.2.3 - Update -build_flags = ${common.build_flags} -src_filter = ${common.src_filter} + ESP8266WiFi ; wifi (Arduino built-in) + Update ; ota (Arduino built-in) + ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp [common:esp32] +extends = common platform = platformio/espressif32@3.2.0 framework = arduino board = nodemcu-32s lib_deps = ${common.lib_deps} - esphome/AsyncTCP-esphome@1.2.2 - Update -build_flags = - ${common.build_flags} -src_filter = - ${common.src_filter} - - + Hash ; ota (Arduino built-in) + esphome/AsyncTCP-esphome@1.2.2 ; async_tcp [env:esp8266] extends = common:esp8266 From c69b88bb554694c4059057717c10ee45f46580df Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:10:42 +0200 Subject: [PATCH 243/285] Fix platformio.ini parser used by container build --- docker/platformio_install_deps.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 6f3e9f28d5..6b5860ffc5 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -3,18 +3,11 @@ # all platformio libraries in the global storage import configparser -import re import subprocess import sys -config = configparser.ConfigParser() +config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) -libs = [] -for line in config['common']['lib_deps'].splitlines(): - # Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment) - m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line) - if m is None: - continue - libs.append(m.group(1)) +libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] -subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) +subprocess.check_call(['platformio', 'lib', '-g', 'uninstall', *libs]) From aed140d80208032ecb6d3d3358b6c48b7dee95be Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 15 Sep 2021 19:13:30 +0200 Subject: [PATCH 244/285] Fix typo --- docker/platformio_install_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 6b5860ffc5..5625bd4d01 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -10,4 +10,4 @@ config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] -subprocess.check_call(['platformio', 'lib', '-g', 'uninstall', *libs]) +subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) From 8f3a739da77e06b5ef18115248ee469ed0caa95f Mon Sep 17 00:00:00 2001 From: Matthew Mazzanti Date: Wed, 15 Sep 2021 13:59:58 -0400 Subject: [PATCH 245/285] Allow transforms and flashes to not update remote_values (#2313) --- esphome/components/light/light_call.cpp | 4 ++-- esphome/components/light/light_state.cpp | 14 ++++++++++---- esphome/components/light/light_state.h | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 178a78a94c..dc1e7d39fb 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -77,7 +77,7 @@ void LightCall::perform() { ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f); } - this->parent_->start_flash_(v, *this->flash_length_); + this->parent_->start_flash_(v, *this->flash_length_, this->publish_); } else if (this->has_transition_()) { // TRANSITION if (this->publish_) { @@ -92,7 +92,7 @@ void LightCall::perform() { this->parent_->stop_effect_(); } - this->parent_->start_transition_(v, *this->transition_length_); + this->parent_->start_transition_(v, *this->transition_length_, this->publish_); } else if (this->has_effect_()) { // EFFECT diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 945d3910d5..d7f539a8d8 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -228,13 +228,16 @@ void LightState::stop_effect_() { this->active_effect_index_ = 0; } -void LightState::start_transition_(const LightColorValues &target, uint32_t length) { +void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) { this->transformer_ = this->output_->create_default_transition(); this->transformer_->setup(this->current_values, target, length); - this->remote_values = target; + + if (set_remote_values) { + this->remote_values = target; + } } -void LightState::start_flash_(const LightColorValues &target, uint32_t length) { +void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) { LightColorValues end_colors = this->remote_values; // If starting a flash if one is already happening, set end values to end values of current flash // Hacky but works @@ -243,7 +246,10 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length) { this->transformer_ = make_unique(*this); this->transformer_->setup(end_colors, target, length); - this->remote_values = target; + + if (set_remote_values) { + this->remote_values = target; + }; } void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index dd42aa76db..f73a4c3b17 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -154,10 +154,10 @@ class LightState : public Nameable, public Component { /// Internal method to stop the current effect (if one is active). void stop_effect_(); /// Internal method to start a transition to the target color with the given length. - void start_transition_(const LightColorValues &target, uint32_t length); + void start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values); /// Internal method to start a flash for the specified amount of time. - void start_flash_(const LightColorValues &target, uint32_t length); + void start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values); /// Internal method to set the color values to target immediately (with no transition). void set_immediately_(const LightColorValues &target, bool set_remote_values); From 2e49039c0187dac827292dae23d1c73786f8b0f7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 19 Sep 2021 14:20:35 +0200 Subject: [PATCH 246/285] Reduce stale/lock gh actions interval (#2341) --- .github/stale.yml | 59 ------------------------------------- .github/workflows/lock.yml | 2 +- .github/workflows/stale.yml | 2 +- 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 225c029bd9..0000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - not-stale - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 10 - -# Limit to only `issues` or `pulls` -only: pulls - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -# pulls: -# daysUntilStale: 30 -# markComment: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index aedb9dd9b3..375b8f1db4 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,7 +2,7 @@ name: Lock on: schedule: - - cron: "0 * * * *" + - cron: '30 0 * * *' workflow_dispatch: permissions: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f26dd32929..712ae1a289 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: Stale on: schedule: - - cron: "0 * * * *" + - cron: '30 0 * * *' workflow_dispatch: permissions: From 64341d1d1887f16df87b88266402c15a109ac4e1 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 20 Sep 2021 04:30:41 +1200 Subject: [PATCH 247/285] Fix MQTT discovery for sensor state_class (#2331) --- esphome/components/sensor/sensor.cpp | 8 ++++---- esphome/components/sensor/sensor.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 128f36fc93..0dc0275715 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,15 +6,15 @@ namespace sensor { static const char *const TAG = "sensor"; -const LogString *state_class_to_string(StateClass state_class) { +std::string state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: - return LOG_STR("measurement"); + return "measurement"; case STATE_CLASS_TOTAL_INCREASING: - return LOG_STR("total_increasing"); + return "total_increasing"; case STATE_CLASS_NONE: default: - return LOG_STR(""); + return ""; } } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 3fc993cb2c..d284f931b1 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,7 +14,7 @@ namespace sensor { if (!(obj)->get_device_class().empty()) { \ ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ - ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, LOG_STR_ARG(state_class_to_string((obj)->get_state_class()))); \ + ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->get_state_class()).c_str()); \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,7 +37,7 @@ enum StateClass : uint8_t { STATE_CLASS_TOTAL_INCREASING = 2, }; -const LogString *state_class_to_string(StateClass state_class); +std::string state_class_to_string(StateClass state_class); /** Base-class for all sensors. * From 68d547595ee2177319af3e2572e07da4798331c6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:20 +0200 Subject: [PATCH 248/285] Light transition fixes (#2320) --- esphome/components/light/addressable_light.cpp | 11 ++++++----- esphome/components/light/light_state.cpp | 3 +++ esphome/components/light/transformers.h | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index a8fa2cd7ac..599d43d8b1 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -62,10 +62,13 @@ void AddressableLightTransformer::start() { } optional AddressableLightTransformer::apply() { - // Don't try to transition over running effects, instead immediately use the target values. write_state() and the - // effects pick up the change from current_values. + float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); + + // When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the + // LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the + // effects which respect it. if (this->light_.is_effect_active()) - return this->target_values_; + return LightColorValues::lerp(this->get_start_values(), this->get_target_values(), smoothed_progress); // Use a specialized transition for addressable lights: instead of using a unified transition for // all LEDs, we use the current state of each LED as the start. @@ -75,8 +78,6 @@ optional AddressableLightTransformer::apply() { // Instead, we "fake" the look of the LERP by using an exponential average over time and using // dynamically-calculated alpha values to match the look. - float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_()); - float denom = (1.0f - smoothed_progress); float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index d7f539a8d8..398546a09f 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -121,6 +121,9 @@ void LightState::loop() { } if (this->transformer_->is_finished()) { + // if the transition has written directly to the output, current_values is outdated, so update it + this->current_values = this->transformer_->get_target_values(); + this->transformer_->stop(); this->transformer_ = nullptr; this->target_state_reached_callback_.call(); diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index d501d53f72..90646f4e61 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -12,12 +12,18 @@ namespace light { class LightTransitionTransformer : public LightTransformer { public: void start() override { - // When turning light on from off state, use colors from target state. + // When turning light on from off state, use target state and only increase brightness from zero. if (!this->start_values_.is_on() && this->target_values_.is_on()) { this->start_values_ = LightColorValues(this->target_values_); this->start_values_.set_brightness(0.0f); } + // When turning light off from on state, use source state and only decrease brightness to zero. + if (this->start_values_.is_on() && !this->target_values_.is_on()) { + this->target_values_ = LightColorValues(this->start_values_); + this->target_values_.set_brightness(0.0f); + } + // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) { this->changing_color_mode_ = true; From f76685fccf50f5bf64de80dd5b2a8295353f1798 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:31:31 +0200 Subject: [PATCH 249/285] Cease using deprecated Cover methods in automations (#2326) --- esphome/components/cover/automation.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 0092f987f2..0b364e1e09 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -11,7 +11,7 @@ template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->open(); } + void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); } protected: Cover *cover_; @@ -21,7 +21,7 @@ template class CloseAction : public Action { public: explicit CloseAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->close(); } + void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); } protected: Cover *cover_; @@ -31,7 +31,7 @@ template class StopAction : public Action { public: explicit StopAction(Cover *cover) : cover_(cover) {} - void play(Ts... x) override { this->cover_->stop(); } + void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); } protected: Cover *cover_; From 30eca885c9c55c92fa2d3b62dbe627b762443e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sun, 19 Sep 2021 18:46:17 +0200 Subject: [PATCH 250/285] Add `esp8266_disable_ssl_support:` config option (#2236) --- esphome/components/http_request/__init__.py | 6 ++++++ esphome/components/http_request/http_request.cpp | 2 ++ esphome/components/http_request/http_request.h | 5 +++++ esphome/const.py | 1 + esphome/core/defines.h | 1 + 5 files changed, 15 insertions(+) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 7dffdae27f..eea2f0d407 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_METHOD, CONF_TRIGGER_ID, CONF_URL, + CONF_ESP8266_DISABLE_SSL_SUPPORT, ) from esphome.core import CORE, Lambda @@ -71,6 +72,9 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(HttpRequestComponent), cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( + cv.only_on_esp8266, cv.boolean + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -98,6 +102,8 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_timeout(config[CONF_TIMEOUT])) cg.add(var.set_useragent(config[CONF_USERAGENT])) + if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: + cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") await cg.register_component(var, config) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 0d4e425147..3f5bf6da87 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -81,6 +81,7 @@ void HttpRequestComponent::send(const std::vector #ifdef ARDUINO_ARCH_ESP8266 std::shared_ptr HttpRequestComponent::get_wifi_client_() { +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS if (this->secure_) { if (this->wifi_client_secure_ == nullptr) { this->wifi_client_secure_ = std::make_shared(); @@ -89,6 +90,7 @@ std::shared_ptr HttpRequestComponent::get_wifi_client_() { } return this->wifi_client_secure_; } +#endif if (this->wifi_client_ == nullptr) { this->wifi_client_ = std::make_shared(); diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 740a4580b4..fa29f8aa3a 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -3,6 +3,7 @@ #include "esphome/components/json/json_util.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include #include #include @@ -13,8 +14,10 @@ #endif #ifdef ARDUINO_ARCH_ESP8266 #include +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS #include #endif +#endif namespace esphome { namespace http_request { @@ -53,7 +56,9 @@ class HttpRequestComponent : public Component { std::list
    headers_; #ifdef ARDUINO_ARCH_ESP8266 std::shared_ptr wifi_client_; +#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS std::shared_ptr wifi_client_secure_; +#endif std::shared_ptr get_wifi_client_(); #endif }; diff --git a/esphome/const.py b/esphome/const.py index 92d18cde3a..a74068ab77 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -208,6 +208,7 @@ CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_ID = "entity_id" +CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 45bb3b82a5..e3976d378e 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -20,6 +20,7 @@ #define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN #define USE_HOMEASSISTANT_TIME +#define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_I2C_MULTIPLEXER #define USE_JSON #define USE_LIGHT From 50da6308110d89b20bf4b1366c1fe296da7a23ca Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 19 Sep 2021 18:46:26 +0200 Subject: [PATCH 251/285] Apply color brightness to addressable light effects (#2321) --- esphome/components/light/addressable_light.cpp | 6 +++--- esphome/components/light/addressable_light.h | 3 +++ esphome/components/light/addressable_light_effect.h | 7 ++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index 599d43d8b1..f3e6c0ef1d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -27,7 +27,7 @@ std::unique_ptr AddressableLight::create_default_transition() return make_unique(*this); } -Color esp_color_from_light_color_values(LightColorValues val) { +Color color_from_light_color_values(LightColorValues val) { auto r = to_uint8_scale(val.get_color_brightness() * val.get_red()); auto g = to_uint8_scale(val.get_color_brightness() * val.get_green()); auto b = to_uint8_scale(val.get_color_brightness() * val.get_blue()); @@ -44,7 +44,7 @@ void AddressableLight::update_state(LightState *state) { return; // don't use LightState helper, gamma correction+brightness is handled by ESPColorView - this->all() = esp_color_from_light_color_values(val); + this->all() = color_from_light_color_values(val); this->schedule_show(); } @@ -54,7 +54,7 @@ void AddressableLightTransformer::start() { return; auto end_values = this->target_values_; - this->target_color_ = esp_color_from_light_color_values(end_values); + this->target_color_ = color_from_light_color_values(end_values); // our transition will handle brightness, disable brightness in correction. this->light_.correction_.set_local_brightness(255); diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index bba2158457..97f4a4687d 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -19,6 +19,9 @@ namespace light { using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphome::Color instead.", "v1.21") = Color; +/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). +Color color_from_light_color_values(LightColorValues val); + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index 1cb29dfa4e..358fe69c23 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -38,11 +38,8 @@ class AddressableLightEffect : public LightEffect { void stop() override { this->get_addressable_()->set_effect_active(false); } virtual void apply(AddressableLight &it, const Color ¤t_color) = 0; void apply() override { - LightColorValues color = this->state_->remote_values; - // not using any color correction etc. that will be handled by the addressable layer - Color current_color = - Color(static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255), static_cast(color.get_white() * 255)); + // not using any color correction etc. that will be handled by the addressable layer through ESPColorCorrection + Color current_color = color_from_light_color_values(this->state_->remote_values); this->apply(*this->get_addressable_(), current_color); } From dbb195691b0ad784ac84d0273b48a69d31bad27c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Sep 2021 19:22:28 +0200 Subject: [PATCH 252/285] Bump pylint from 2.10.2 to 2.11.1 (#2334) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto winter --- esphome/__main__.py | 14 +-- esphome/components/binary_sensor/__init__.py | 16 ++-- esphome/components/deep_sleep/__init__.py | 3 +- .../components/esp32_ble_beacon/__init__.py | 4 +- .../components/esp32_ble_tracker/__init__.py | 13 +-- esphome/components/font/__init__.py | 5 +- esphome/components/lcd_gpio/display.py | 3 +- esphome/components/ledc/output.py | 6 +- esphome/components/light/effects.py | 6 +- esphome/components/logger/__init__.py | 8 +- esphome/components/mqtt/__init__.py | 7 +- esphome/components/neopixelbus/light.py | 8 +- esphome/components/nextion/base_component.py | 6 +- esphome/components/ntc/sensor.py | 4 +- esphome/components/packages/__init__.py | 3 +- esphome/components/partition/light.py | 3 +- esphome/components/pmsx003/sensor.py | 4 +- esphome/components/remote_base/__init__.py | 16 ++-- esphome/components/rotary_encoder/sensor.py | 3 +- esphome/components/sensor/__init__.py | 6 +- esphome/components/substitutions/__init__.py | 7 +- esphome/components/time/__init__.py | 36 +++----- esphome/components/wifi/wpa2_eap.py | 1 + esphome/config.py | 76 ++++++---------- esphome/config_validation.py | 89 +++++++------------ esphome/core/__init__.py | 18 ++-- esphome/core/config.py | 15 +--- esphome/cpp_generator.py | 12 ++- esphome/cpp_helpers.py | 4 +- esphome/dashboard/dashboard.py | 46 +++++----- esphome/espota2.py | 6 +- esphome/helpers.py | 8 +- esphome/mqtt.py | 9 +- esphome/pins.py | 15 ++-- esphome/platformio_api.py | 16 ++-- esphome/storage_json.py | 4 +- esphome/wizard.py | 84 ++++++----------- esphome/writer.py | 10 +-- esphome/yaml_util.py | 7 +- requirements_test.txt | 2 +- 40 files changed, 219 insertions(+), 384 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 121fa7cc9e..ba1019869d 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -72,7 +72,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api if default == "OTA": return CORE.address if show_mqtt and "mqtt" in CORE.config: - options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) + options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT")) if default == "OTA": return "MQTT" if default is not None: @@ -415,30 +415,30 @@ def command_update_all(args): click.echo(f"{half_line}{middle_text}{half_line}") for f in files: - print("Updating {}".format(color(Fore.CYAN, f))) + print(f"Updating {color(Fore.CYAN, f)}") print("-" * twidth) print() rc = run_external_process( "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" ) if rc == 0: - print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) + print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}") success[f] = True else: - print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f)) + print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}") success[f] = False print() print() print() - print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY"))) + print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]") failed = 0 for f in files: if success[f]: - print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS"))) + print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}") else: - print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED"))) + print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}") failed += 1 return failed diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 9cd2a045ff..60ac4303a7 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -231,17 +231,16 @@ def parse_multi_click_timing_str(value): parts = value.lower().split(" ") if len(parts) != 5: raise cv.Invalid( - "Multi click timing grammar consists of exactly 5 words, not {}" - "".format(len(parts)) + f"Multi click timing grammar consists of exactly 5 words, not {len(parts)}" ) try: state = cv.boolean(parts[0]) except cv.Invalid: # pylint: disable=raise-missing-from - raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) + raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}") if parts[1] != "for": - raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) + raise cv.Invalid(f"Second word must be 'for', got {parts[1]}") if parts[2] == "at": if parts[3] == "least": @@ -250,8 +249,7 @@ def parse_multi_click_timing_str(value): key = CONF_MAX_LENGTH else: raise cv.Invalid( - "Third word after at must either be 'least' or 'most', got {}" - "".format(parts[3]) + f"Third word after at must either be 'least' or 'most', got {parts[3]}" ) try: length = cv.positive_time_period_milliseconds(parts[4]) @@ -296,13 +294,11 @@ def validate_multi_click_timing(value): new_state = v_.get(CONF_STATE, not state) if new_state == state: raise cv.Invalid( - "Timings must have alternating state. Indices {} and {} have " - "the same state {}".format(i, i + 1, state) + f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}" ) if max_length is not None and max_length < min_length: raise cv.Invalid( - "Max length ({}) must be larger than min length ({})." - "".format(max_length, min_length) + f"Max length ({max_length}) must be larger than min length ({min_length})." ) state = new_state diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index b7bf27e79a..3f59ad52cf 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -16,8 +16,7 @@ def validate_pin_number(value): valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( - "Only pins {} support wakeup" - "".format(", ".join(str(x) for x in valid_pins)) + f"Only pins {', '.join(str(x) for x in valid_pins)} support wakeup" ) return value diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 3e00692d3a..cd0cb4bed6 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -24,9 +24,7 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): uuid = config[CONF_UUID].hex - uuid_arr = [ - cg.RawExpression("0x{}".format(uuid[i : i + 2])) for i in range(0, len(uuid), 2) - ] + uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] var = cg.new_Pvariable(config[CONF_ID], uuid_arr) await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index fec0f6dcfb..162a4dee89 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -51,8 +51,7 @@ def validate_scan_parameters(config): if window > interval: raise cv.Invalid( - "Scan window ({}) needs to be smaller than scan interval ({})" - "".format(window, interval) + f"Scan window ({window}) needs to be smaller than scan interval ({interval})" ) if interval.total_milliseconds * 3 > duration.total_milliseconds: @@ -97,9 +96,7 @@ def bt_uuid(value): ) return value raise cv.Invalid( - "Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format( - bt_uuid16_format, bt_uuid32_format, bt_uuid128_format - ) + f"Service UUID must be in 16 bit '{bt_uuid16_format}', 32 bit '{bt_uuid32_format}', or 128 bit '{bt_uuid128_format}' format" ) @@ -112,9 +109,7 @@ def as_hex_array(value): cpp_array = [ f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] ] - return cg.RawExpression( - "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(cpp_array)) - ) + return cg.RawExpression(f"(uint8_t*)(const uint8_t[16]){{{','.join(cpp_array)}}}") def as_reversed_hex_array(value): @@ -123,7 +118,7 @@ def as_reversed_hex_array(value): f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)] ] return cg.RawExpression( - "(uint8_t*)(const uint8_t[16]){{{}}}".format(",".join(reversed(cpp_array))) + f"(uint8_t*)(const uint8_t[16]){{{','.join(reversed(cpp_array))}}}" ) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 16a7d8a612..e47a6f38af 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -61,8 +61,7 @@ def validate_pillow_installed(value): def validate_truetype_file(value): if value.endswith(".zip"): # for Google Fonts downloads raise cv.Invalid( - "Please unzip the font archive '{}' first and then use the .ttf files " - "inside.".format(value) + f"Please unzip the font archive '{value}' first and then use the .ttf files inside." ) if not value.endswith(".ttf"): raise cv.Invalid( @@ -131,7 +130,7 @@ async def to_code(config): ("a_char", glyph), ( "data", - cg.RawExpression(str(prog_arr) + " + " + str(glyph_args[glyph][0])), + cg.RawExpression(f"{str(prog_arr)} + {str(glyph_args[glyph][0])}"), ), ("offset_x", glyph_args[glyph][1]), ("offset_y", glyph_args[glyph][2]), diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 6b0a2f69de..9fb635eafa 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -20,8 +20,7 @@ GPIOLCDDisplay = lcd_gpio_ns.class_("GPIOLCDDisplay", lcd_base.LCDDisplay) def validate_pin_length(value): if len(value) != 4 and len(value) != 8: raise cv.Invalid( - "LCD Displays can either operate in 4-pin or 8-pin mode," - "not {}-pin mode".format(len(value)) + f"LCD Displays can either operate in 4-pin or 8-pin mode,not {len(value)}-pin mode" ) return value diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 83b1c6f096..0f5f186ff5 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -28,13 +28,11 @@ def validate_frequency(value): max_freq = calc_max_frequency(1) if value < min_freq: raise cv.Invalid( - "This frequency setting is not possible, please choose a higher " - "frequency (at least {}Hz)".format(int(min_freq)) + f"This frequency setting is not possible, please choose a higher frequency (at least {int(min_freq)}Hz)" ) if value > max_freq: raise cv.Invalid( - "This frequency setting is not possible, please choose a lower " - "frequency (at most {}Hz)".format(int(max_freq)) + f"This frequency setting is not possible, please choose a lower frequency (at most {int(max_freq)}Hz)" ) return value diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index be558a8140..4b2209c833 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -490,8 +490,7 @@ def validate_effects(allowed_effects): if key not in allowed_effects: errors.append( cv.Invalid( - "The effect '{}' is not allowed for this " - "light type".format(key), + f"The effect '{key}' is not allowed for this light type", [i], ) ) @@ -500,8 +499,7 @@ def validate_effects(allowed_effects): if name in names: errors.append( cv.Invalid( - "Found the effect name '{}' twice. All effects must have " - "unique names".format(name), + f"Found the effect name '{name}' twice. All effects must have unique names", [i], ) ) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 55178941ec..b821e8c5d8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -86,8 +86,7 @@ def validate_local_no_higher_than_global(value): for tag, level in value.get(CONF_LOGS, {}).items(): if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level): raise EsphomeError( - "The local log level {} for {} must be less severe than the " - "global log level {}.".format(level, tag, global_level) + f"The local log level {level} for {tag} must be less severe than the global log level {global_level}." ) return value @@ -145,7 +144,7 @@ async def to_code(config): level = config[CONF_LEVEL] cg.add_define("USE_LOGGER") this_severity = LOG_LEVEL_SEVERITY.index(level) - cg.add_build_flag("-DESPHOME_LOG_LEVEL={}".format(LOG_LEVELS[level])) + cg.add_build_flag(f"-DESPHOME_LOG_LEVEL={LOG_LEVELS[level]}") verbose_severity = LOG_LEVEL_SEVERITY.index("VERBOSE") very_verbose_severity = LOG_LEVEL_SEVERITY.index("VERY_VERBOSE") @@ -220,8 +219,7 @@ def validate_printf(value): matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) if len(matches) != len(value[CONF_ARGS]): raise cv.Invalid( - "Found {} printf-patterns ({}), but {} args were given!" - "".format(len(matches), ", ".join(matches), len(value[CONF_ARGS])) + f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!" ) return value diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 19f78641df..73ee50ee65 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -266,8 +266,7 @@ async def to_code(config): if CONF_SSL_FINGERPRINTS in config: for fingerprint in config[CONF_SSL_FINGERPRINTS]: arr = [ - cg.RawExpression("0x{}".format(fingerprint[i : i + 2])) - for i in range(0, 40, 2) + cg.RawExpression(f"0x{fingerprint[i:i + 2]}") for i in range(0, 40, 2) ] cg.add(var.add_ssl_fingerprint(arr)) cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") @@ -353,9 +352,7 @@ def get_default_topic_for(data, component_type, name, suffix): sanitized_name = "".join( x for x in name.lower().replace(" ", "_") if x in allowlist ) - return "{}/{}/{}/{}".format( - data.topic_prefix, component_type, sanitized_name, suffix - ) + return f"{data.topic_prefix}/{component_type}/{sanitized_name}/{suffix}" async def register_mqtt_component(var, config): diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 5eee5210f9..a86b4e4588 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -40,7 +40,7 @@ def validate_type(value): raise cv.Invalid("Must have B in type") rest = set(value) - set("RGBW") if rest: - raise cv.Invalid("Type has invalid color: {}".format(", ".join(rest))) + raise cv.Invalid(f"Type has invalid color: {', '.join(rest)}") if len(set(value)) != len(value): raise cv.Invalid("Type has duplicate color!") return value @@ -95,9 +95,7 @@ def validate_method_pin(value): for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): if opt in value and value[opt] not in pins_: raise cv.Invalid( - "Method {} only supports pin(s) {}".format( - method, ", ".join(f"GPIO{x}" for x in pins_) - ), + f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}", path=[CONF_METHOD], ) return value @@ -139,7 +137,7 @@ def format_method(config): if config[CONF_INVERT]: if method == "ESP8266_DMA": - variant = "Inverted" + variant + variant = f"Inverted{variant}" else: variant += "Inverted" diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index ce567bc3f1..75694ee4b2 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -30,16 +30,14 @@ CONF_FONT_ID = "font_id" def NextionName(value): - valid_chars = ascii_letters + digits + "." + valid_chars = f"{ascii_letters + digits}." if not isinstance(value, str) or len(value) > 29: raise cv.Invalid("Must be a string less than 29 characters") for char in value: if char not in valid_chars: raise cv.Invalid( - "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( - char - ) + f"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{char}' cannot be used." ) return value diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index bf819ffd16..660208635c 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -105,9 +105,7 @@ def process_calibration(value): a, b, c = calc_steinhart_hart(value) else: raise cv.Invalid( - "Calibration parameter accepts either a list for steinhart-hart " - "calibration, or mapping for b-constant calibration, " - "not {}".format(type(value)) + f"Calibration parameter accepts either a list for steinhart-hart calibration, or mapping for b-constant calibration, not {type(value)}" ) return { diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 330ffc2bf2..df0f0de13d 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -151,8 +151,7 @@ def do_packages_pass(config: dict): packages = CONFIG_SCHEMA(packages) if not isinstance(packages, dict): raise cv.Invalid( - "Packages must be a key to value mapping, got {} instead" - "".format(type(packages)) + f"Packages must be a key to value mapping, got {type(packages)} instead" ) for package_name, package_config in packages.items(): diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 5ded6b906c..06bee2143e 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -20,8 +20,7 @@ PartitionLightOutput = partitions_ns.class_( def validate_from_to(value): if value[CONF_FROM] > value[CONF_TO]: raise cv.Invalid( - "From ({}) must not be larger than to ({})" - "".format(value[CONF_FROM], value[CONF_TO]) + f"From ({value[CONF_FROM]}) must not be larger than to ({value[CONF_TO]})" ) return value diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 8b9e5c9af2..350117a235 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -63,9 +63,7 @@ SENSORS_TO_TYPE = { def validate_pmsx003_sensors(value): for key, types in SENSORS_TO_TYPE.items(): if key in value and value[CONF_TYPE] not in types: - raise cv.Invalid( - "{} does not have {} sensor!".format(value[CONF_TYPE], key) - ) + raise cv.Invalid(f"{value[CONF_TYPE]} does not have {key} sensor!") return value diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d76dc6bc34..537ae2283c 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -491,8 +491,7 @@ def validate_raw_alternating(value): if i != 0: if this_negative == last_negative: raise cv.Invalid( - "Values must alternate between being positive and negative, " - "please see index {} and {}".format(i, i + 1), + f"Values must alternate between being positive and negative, please see index {i} and {i + 1}", [i], ) last_negative = this_negative @@ -619,13 +618,11 @@ def validate_rc_switch_code(value): for c in value: if c not in ("0", "1"): raise cv.Invalid( - "Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed" - "".format(c) + f"Invalid RCSwitch code character '{c}'. Only '0' and '1' are allowed" ) if len(value) > 64: raise cv.Invalid( - "Maximum length for RCSwitch codes is 64, code '{}' has length {}" - "".format(value, len(value)) + f"Maximum length for RCSwitch codes is 64, code '{value}' has length {len(value)}" ) if not value: raise cv.Invalid("RCSwitch code must not be empty") @@ -638,14 +635,11 @@ def validate_rc_switch_raw_code(value): for c in value: if c not in ("0", "1", "x"): raise cv.Invalid( - "Invalid RCSwitch raw code character '{}'.Only '0', '1' and 'x' are allowed".format( - c - ) + f"Invalid RCSwitch raw code character '{c}'.Only '0', '1' and 'x' are allowed" ) if len(value) > 64: raise cv.Invalid( - "Maximum length for RCSwitch raw codes is 64, code '{}' has length {}" - "".format(value, len(value)) + f"Maximum length for RCSwitch raw codes is 64, code '{value}' has length {len(value)}" ) if not value: raise cv.Invalid("RCSwitch raw code must not be empty") diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index 311e63a7ba..e82b9d5f13 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -49,8 +49,7 @@ def validate_min_max_value(config): max_val = config[CONF_MAX_VALUE] if min_val >= max_val: raise cv.Invalid( - "Max value {} must be smaller than min value {}" - "".format(max_val, min_val) + f"Max value {max_val} must be smaller than min value {min_val}" ) return config diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index fd278be51e..ce4aafb3b0 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -109,8 +109,7 @@ def validate_send_first_at(value): send_every = value[CONF_SEND_EVERY] if send_first_at is not None and send_first_at > send_every: raise cv.Invalid( - "send_first_at must be smaller than or equal to send_every! {} <= {}" - "".format(send_first_at, send_every) + f"send_first_at must be smaller than or equal to send_every! {send_first_at} <= {send_every}" ) return value @@ -459,8 +458,7 @@ CONF_DEGREE = "degree" def validate_calibrate_polynomial(config): if config[CONF_DEGREE] >= len(config[CONF_DATAPOINTS]): raise cv.Invalid( - "Degree is too high! Maximum possible degree with given datapoints is " - "{}".format(len(config[CONF_DATAPOINTS]) - 1), + f"Degree is too high! Maximum possible degree with given datapoints is {len(config[CONF_DATAPOINTS]) - 1}", [CONF_DEGREE], ) return config diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index e7a5e24ee6..0cef417c15 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -25,8 +25,7 @@ def validate_substitution_key(value): for char in value: if char not in VALID_SUBSTITUTIONS_CHARACTERS: raise cv.Invalid( - "Substitution must only consist of upper/lowercase characters, the underscore " - "and numbers. The character '{}' cannot be used".format(char) + f"Substitution must only consist of upper/lowercase characters, the underscore and numbers. The character '{char}' cannot be used" ) return value @@ -42,6 +41,7 @@ async def to_code(config): pass +# pylint: disable=consider-using-f-string VARIABLE_PROG = re.compile( "\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS) ) @@ -133,8 +133,7 @@ def do_substitution_pass(config, command_line_substitutions): with cv.prepend_path("substitutions"): if not isinstance(substitutions, dict): raise cv.Invalid( - "Substitutions must be a key to value mapping, got {}" - "".format(type(substitutions)) + f"Substitutions must be a key to value mapping, got {type(substitutions)}" ) replace_keys = [] diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 4872c89f88..d548575d72 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -67,9 +67,7 @@ def _week_of_month(dt): def _tz_dst_str(dt): td = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) - return "M{}.{}.{}/{}".format( - dt.month, _week_of_month(dt), dt.isoweekday() % 7, _tz_timedelta(td) - ) + return f"M{dt.month}.{_week_of_month(dt)}.{dt.isoweekday() % 7}/{_tz_timedelta(td)}" def _safe_tzname(tz, dt): @@ -88,7 +86,7 @@ def _non_dst_tz(tz, dt): _LOGGER.info( "Detected timezone '%s' with UTC offset %s", tzname, _tz_timedelta(utcoffset) ) - tzbase = "{}{}".format(tzname, _tz_timedelta(-1 * utcoffset)) + tzbase = f"{tzname}{_tz_timedelta(-1 * utcoffset)}" return tzbase @@ -129,14 +127,9 @@ def convert_tz(pytz_obj): dst_ends_utc = transition_times[idx2] dst_ends_local = dst_ends_utc + utcoffset_on - tzbase = "{}{}".format(tzname_off, _tz_timedelta(-1 * utcoffset_off)) + tzbase = f"{tzname_off}{_tz_timedelta(-1 * utcoffset_off)}" - tzext = "{}{},{},{}".format( - tzname_on, - _tz_timedelta(-1 * utcoffset_on), - _tz_dst_str(dst_begins_local), - _tz_dst_str(dst_ends_local), - ) + tzext = f"{tzname_on}{_tz_timedelta(-1 * utcoffset_on)},{_tz_dst_str(dst_begins_local)},{_tz_dst_str(dst_ends_local)}" _LOGGER.info( "Detected timezone '%s' with UTC offset %s and daylight saving time from " "%s to %s", @@ -176,9 +169,7 @@ def _parse_cron_part(part, min_value, max_value, special_mapping): data = part.split("/") if len(data) > 2: raise cv.Invalid( - "Can't have more than two '/' in one time expression, got {}".format( - part - ) + f"Can't have more than two '/' in one time expression, got {part}" ) offset, repeat = data offset_n = 0 @@ -194,18 +185,14 @@ def _parse_cron_part(part, min_value, max_value, special_mapping): except ValueError: # pylint: disable=raise-missing-from raise cv.Invalid( - "Repeat for '/' time expression must be an integer, got {}".format( - repeat - ) + f"Repeat for '/' time expression must be an integer, got {repeat}" ) return set(range(offset_n, max_value + 1, repeat_n)) if "-" in part: data = part.split("-") if len(data) > 2: raise cv.Invalid( - "Can't have more than two '-' in range time expression '{}'".format( - part - ) + f"Can't have more than two '-' in range time expression '{part}'" ) begin, end = data begin_n = _parse_cron_int( @@ -233,13 +220,11 @@ def cron_expression_validator(name, min_value, max_value, special_mapping=None): for v in value: if not isinstance(v, int): raise cv.Invalid( - "Expected integer for {} '{}', got {}".format(v, name, type(v)) + f"Expected integer for {v} '{name}', got {type(v)}" ) if v < min_value or v > max_value: raise cv.Invalid( - "{} {} is out of range (min={} max={}).".format( - name, v, min_value, max_value - ) + f"{name} {v} is out of range (min={min_value} max={max_value})." ) return list(sorted(value)) value = cv.string(value) @@ -295,8 +280,7 @@ def validate_cron_raw(value): value = value.split(" ") if len(value) != 6: raise cv.Invalid( - "Cron expression must consist of exactly 6 space-separated parts, " - "not {}".format(len(value)) + f"Cron expression must consist of exactly 6 space-separated parts, not {len(value)}" ) seconds, minutes, hours, days_of_month, months, days_of_week = value return { diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 071737ccd7..3cb60e6175 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -57,6 +57,7 @@ def wrapped_load_pem_private_key(value, password): def read_relative_config_path(value): + # pylint: disable=unspecified-encoding return Path(CORE.relative_config_path(value)).read_text() diff --git a/esphome/config.py b/esphome/config.py index de261f7eba..e49293e6b2 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -42,7 +42,7 @@ def iter_components(config): yield domain, component, conf if component.is_platform_component: for p_config in conf: - p_name = "{}.{}".format(domain, p_config[CONF_PLATFORM]) + p_name = f"{domain}.{p_config[CONF_PLATFORM]}" platform = get_platform(domain, p_config[CONF_PLATFORM]) yield p_name, platform, p_config @@ -238,10 +238,7 @@ def do_id_pass(result): # type: (Config) -> None # No declared ID with this name import difflib - error = ( - "Couldn't find ID '{}'. Please check you have defined " - "an ID with that name in your configuration.".format(id.id) - ) + error = f"Couldn't find ID '{id.id}'. Please check you have defined an ID with that name in your configuration." # Find candidates matches = difflib.get_close_matches( id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] @@ -257,9 +254,7 @@ def do_id_pass(result): # type: (Config) -> None continue if not match.type.inherits_from(id.type): result.add_str_error( - "ID '{}' of type {} doesn't inherit from {}. Please " - "double check your ID is pointing to the correct value" - "".format(id.id, match.type, id.type), + f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. Please double check your ID is pointing to the correct value", path, ) @@ -497,7 +492,7 @@ def validate_config(config, command_line_substitutions): for dependency in comp.dependencies: if dependency not in config: result.add_str_error( - "Component {} requires component {}" "".format(domain, dependency), + f"Component {domain} requires component {dependency}", path, ) success = False @@ -508,8 +503,7 @@ def validate_config(config, command_line_substitutions): for conflict in comp.conflicts_with: if conflict in config: result.add_str_error( - "Component {} cannot be used together with component {}" - "".format(domain, conflict), + f"Component {domain} cannot be used together with component {conflict}", path, ) success = False @@ -518,7 +512,7 @@ def validate_config(config, command_line_substitutions): if CORE.esp_platform not in comp.esp_platforms: result.add_str_error( - "Component {} doesn't support {}.".format(domain, CORE.esp_platform), + f"Component {domain} doesn't support {CORE.esp_platform}.", path, ) continue @@ -529,8 +523,7 @@ def validate_config(config, command_line_substitutions): and not isinstance(conf, core.AutoLoad) ): result.add_str_error( - "Component {} cannot be loaded via YAML " - "(no CONFIG_SCHEMA).".format(domain), + f"Component {domain} cannot be loaded via YAML (no CONFIG_SCHEMA).", path, ) continue @@ -540,8 +533,7 @@ def validate_config(config, command_line_substitutions): result[domain] = conf = [conf] if not isinstance(comp.multi_conf, bool) and len(conf) > comp.multi_conf: result.add_str_error( - "Component {} supports a maximum of {} " - "entries ({} found).".format(domain, comp.multi_conf, len(conf)), + f"Component {domain} supports a maximum of {comp.multi_conf} entries ({len(conf)} found).", path, ) continue @@ -636,18 +628,14 @@ def _format_vol_invalid(ex, config): if isinstance(ex, ExtraKeysInvalid): if ex.candidates: - message += "[{}] is an invalid option for [{}]. Did you mean {}?".format( - ex.path[-1], paren, ", ".join(f"[{x}]" for x in ex.candidates) - ) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]. Did you mean {', '.join(f'[{x}]' for x in ex.candidates)}?" else: - message += "[{}] is an invalid option for [{}]. Please check the indentation.".format( - ex.path[-1], paren - ) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]. Please check the indentation." elif "extra keys not allowed" in str(ex): - message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) + message += f"[{ex.path[-1]}] is an invalid option for [{paren}]." elif isinstance(ex, vol.RequiredFieldInvalid): if ex.msg == "required key not provided": - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + message += f"'{ex.path[-1]}' is a required option for [{paren}]." else: # Required has set a custom error message message += ex.msg @@ -700,7 +688,7 @@ def line_info(config, path, highlight=True): obj = config.get_deepest_document_range_for_path(path) if obj: mark = obj.start_mark - source = "[source {}:{}]".format(mark.document, mark.line + 1) + source = f"[source {mark.document}:{mark.line + 1}]" return color(Fore.CYAN, source) return "None" @@ -724,9 +712,7 @@ def dump_dict(config, path, at_root=True): if at_root: error = config.get_error_for_path(path) if error is not None: - ret += ( - "\n" + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" if isinstance(conf, (list, tuple)): multiline = True @@ -738,11 +724,7 @@ def dump_dict(config, path, at_root=True): path_ = path + [i] error = config.get_error_for_path(path_) if error is not None: - ret += ( - "\n" - + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) - + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" sep = "- " if config.is_in_error_path(path_): @@ -751,10 +733,10 @@ def dump_dict(config, path, at_root=True): msg = indent(msg) inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if inf is not None: - msg = inf + "\n" + msg + msg = f"{inf}\n{msg}" elif msg: msg = msg[2:] - ret += sep + msg + "\n" + ret += f"{sep + msg}\n" elif isinstance(conf, dict): multiline = True if not conf: @@ -765,11 +747,7 @@ def dump_dict(config, path, at_root=True): path_ = path + [k] error = config.get_error_for_path(path_) if error is not None: - ret += ( - "\n" - + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) - + "\n" - ) + ret += f"\n{color(Fore.BOLD_RED, _format_vol_invalid(error, config))}\n" st = f"{k}: " if config.is_in_error_path(path_): @@ -778,30 +756,30 @@ def dump_dict(config, path, at_root=True): inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) if m: - msg = "\n" + indent(msg) + msg = f"\n{indent(msg)}" if inf is not None: if m: - msg = " " + inf + msg + msg = f" {inf}{msg}" else: - msg = msg + " " + inf - ret += st + msg + "\n" + msg = f"{msg} {inf}" + ret += f"{st + msg}\n" elif isinstance(conf, str): if is_secret(conf): - conf = "!secret {}".format(is_secret(conf)) + conf = f"!secret {is_secret(conf)}" if not conf: conf += "''" if len(conf) > 80: - conf = "|-\n" + indent(conf) + conf = f"|-\n{indent(conf)}" error = config.get_error_for_path(path) col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, str(conf)) elif isinstance(conf, core.Lambda): if is_secret(conf): - conf = "!secret {}".format(is_secret(conf)) + conf = f"!secret {is_secret(conf)}" - conf = "!lambda |-\n" + indent(str(conf.value)) + conf = f"!lambda |-\n{indent(str(conf.value))}" error = config.get_error_for_path(path) col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, conf) @@ -860,7 +838,7 @@ def read_config(command_line_substitutions): errstr = color(Fore.BOLD_RED, f"{domain}:") errline = line_info(res, path) if errline: - errstr += " " + errline + errstr += f" {errline}" safe_print(errstr) safe_print(indent(dump_dict(res, path)[0])) return None diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fb659c41ea..a864c65dc7 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -277,8 +277,7 @@ def string_strict(value): if isinstance(value, str): return value raise Invalid( - "Must be string, got {}. did you forget putting quotes " - "around the value?".format(type(value)) + f"Must be string, got {type(value)}. did you forget putting quotes around the value?" ) @@ -311,8 +310,7 @@ def boolean(value): if value in ("false", "no", "off", "disable"): return False raise Invalid( - "Expected boolean value, but cannot convert {} to a boolean. " - "Please use 'true' or 'false'".format(value) + f"Expected boolean value, but cannot convert {value} to a boolean. Please use 'true' or 'false'" ) @@ -358,8 +356,7 @@ def int_(value): if int(value) == value: return int(value) raise Invalid( - "This option only accepts integers with no fractional part. Please remove " - "the fractional part from {}".format(value) + f"This option only accepts integers with no fractional part. Please remove the fractional part from {value}" ) value = string_strict(value).lower() base = 10 @@ -424,20 +421,17 @@ def validate_id_name(value): raise Invalid( "Dashes are not supported in IDs, please use underscores instead." ) - valid_chars = ascii_letters + digits + "_" + valid_chars = f"{ascii_letters + digits}_" for char in value: if char not in valid_chars: raise Invalid( - "IDs must only consist of upper/lowercase characters, the underscore" - "character and numbers. The character '{}' cannot be used" - "".format(char) + f"IDs must only consist of upper/lowercase characters, the underscorecharacter and numbers. The character '{char}' cannot be used" ) if value in RESERVED_IDS: raise Invalid(f"ID '{value}' is reserved internally and cannot be used") if value in CORE.loaded_integrations: raise Invalid( - "ID '{}' conflicts with the name of an esphome integration, please use " - "another ID name.".format(value) + f"ID '{value}' conflicts with the name of an esphome integration, please use another ID name." ) return value @@ -525,7 +519,7 @@ def has_at_least_one_key(*keys): raise Invalid("expected dictionary") if not any(k in keys for k in obj): - raise Invalid("Must contain at least one of {}.".format(", ".join(keys))) + raise Invalid(f"Must contain at least one of {', '.join(keys)}.") return obj return validate @@ -540,9 +534,9 @@ def has_exactly_one_key(*keys): number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) + raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") if number < 1: - raise Invalid("Must contain exactly one of {}.".format(", ".join(keys))) + raise Invalid(f"Must contain exactly one of {', '.join(keys)}.") return obj return validate @@ -557,7 +551,7 @@ def has_at_most_one_key(*keys): number = sum(k in keys for k in obj) if number > 1: - raise Invalid("Cannot specify more than one of {}.".format(", ".join(keys))) + raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") return obj return validate @@ -572,9 +566,7 @@ def has_none_or_all_keys(*keys): number = sum(k in keys for k in obj) if number != 0 and number != len(keys): - raise Invalid( - "Must specify either none or all of {}.".format(", ".join(keys)) - ) + raise Invalid(f"Must specify either none or all of {', '.join(keys)}.") return obj return validate @@ -632,8 +624,7 @@ def time_period_str_unit(value): if isinstance(value, int): raise Invalid( - "Don't know what '{0}' means as it has no time *unit*! Did you mean " - "'{0}s'?".format(value) + f"Don't know what '{value}' means as it has no time *unit*! Did you mean '{value}s'?" ) if isinstance(value, TimePeriod): value = str(value) @@ -659,7 +650,7 @@ def time_period_str_unit(value): match = re.match(r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*)$", value) if match is None: - raise Invalid("Expected time period with unit, " "got {}".format(value)) + raise Invalid(f"Expected time period with unit, got {value}") kwarg = unit_to_kwarg[one_of(*unit_to_kwarg)(match.group(2))] return TimePeriod(**{kwarg: float(match.group(1))}) @@ -796,7 +787,7 @@ METRIC_SUFFIXES = { def float_with_unit(quantity, regex_suffix, optional_unit=False): pattern = re.compile( - r"^([-+]?[0-9]*\.?[0-9]*)\s*(\w*?)" + regex_suffix + r"$", re.UNICODE + f"^([-+]?[0-9]*\\.?[0-9]*)\\s*(\\w*?){regex_suffix}$", re.UNICODE ) def validator(value): @@ -812,7 +803,7 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False): mantissa = float(match.group(1)) if match.group(2) not in METRIC_SUFFIXES: - raise Invalid("Invalid {} suffix {}".format(quantity, match.group(2))) + raise Invalid(f"Invalid {quantity} suffix {match.group(2)}") multiplier = METRIC_SUFFIXES[match.group(2)] return mantissa * multiplier @@ -879,12 +870,11 @@ def validate_bytes(value): mantissa = int(match.group(1)) if match.group(2) not in METRIC_SUFFIXES: - raise Invalid("Invalid metric suffix {}".format(match.group(2))) + raise Invalid(f"Invalid metric suffix {match.group(2)}") multiplier = METRIC_SUFFIXES[match.group(2)] if multiplier < 1: raise Invalid( - "Only suffixes with positive exponents are supported. " - "Got {}".format(match.group(2)) + f"Only suffixes with positive exponents are supported. Got {match.group(2)}" ) return int(mantissa * multiplier) @@ -1184,10 +1174,8 @@ def one_of(*values, **kwargs): option = str(value) matches = difflib.get_close_matches(option, options_) if matches: - raise Invalid( - "Unknown value '{}', did you mean {}?" - "".format(value, ", ".join(f"'{x}'" for x in matches)) - ) + matches_str = ", ".join(f"'{x}'" for x in matches) + raise Invalid(f"Unknown value '{value}', did you mean {matches_str}?") raise Invalid(f"Unknown value '{value}', valid options are {options}.") return value @@ -1229,13 +1217,10 @@ def lambda_(value): entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) if len(entity_id_parts) != 1: entity_ids = " ".join( - "'{}'".format(entity_id_parts[i]) for i in range(1, len(entity_id_parts), 2) + f"'{entity_id_parts[i]}'" for i in range(1, len(entity_id_parts), 2) ) raise Invalid( - "Lambda contains reference to entity-id-style ID {}. " - "The id() wrapper only works for ESPHome-internal types. For importing " - "states from Home Assistant use the 'homeassistant' sensor platforms." - "".format(entity_ids) + f"Lambda contains reference to entity-id-style ID {entity_ids}. The id() wrapper only works for ESPHome-internal types. For importing states from Home Assistant use the 'homeassistant' sensor platforms." ) return value @@ -1259,9 +1244,7 @@ def returning_lambda(value): def dimensions(value): if isinstance(value, list): if len(value) != 2: - raise Invalid( - "Dimensions must have a length of two, not {}".format(len(value)) - ) + raise Invalid(f"Dimensions must have a length of two, not {len(value)}") try: width, height = int(value[0]), int(value[1]) except ValueError: @@ -1301,19 +1284,16 @@ def directory(value): if data["content"]: return value raise Invalid( - "Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.exists(path): raise Invalid( - "Could not find directory '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find directory '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.isdir(path): raise Invalid( - "Path '{}' is not a directory (full path: {})." - "".format(path, os.path.abspath(path)) + f"Path '{path}' is not a directory (full path: {os.path.abspath(path)})." ) return value @@ -1340,19 +1320,16 @@ def file_(value): if data["content"]: return value raise Invalid( - "Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.exists(path): raise Invalid( - "Could not find file '{}'. Please make sure it exists (full path: {})." - "".format(path, os.path.abspath(path)) + f"Could not find file '{path}'. Please make sure it exists (full path: {os.path.abspath(path)})." ) if not os.path.isfile(path): raise Invalid( - "Path '{}' is not a file (full path: {})." - "".format(path, os.path.abspath(path)) + f"Path '{path}' is not a file (full path: {os.path.abspath(path)})." ) return value @@ -1405,7 +1382,7 @@ def typed_schema(schemas, **kwargs): value = value.copy() schema_option = value.pop(key, default_schema_option) if schema_option is None: - raise Invalid(key + " not specified!") + raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) value = schemas[key_v](value) value[key] = key_v @@ -1498,8 +1475,7 @@ def validate_registry_entry(name, registry): value = {value: {}} if not isinstance(value, dict): raise Invalid( - "{} must consist of key-value mapping! Got {}" - "".format(name.title(), value) + f"{name.title()} must consist of key-value mapping! Got {value}" ) key = next((x for x in value if x not in ignore_keys), None) if key is None: @@ -1509,9 +1485,8 @@ def validate_registry_entry(name, registry): key2 = next((x for x in value if x != key and x not in ignore_keys), None) if key2 is not None: raise Invalid( - "Cannot have two {0}s in one item. Key '{1}' overrides '{2}'! " - "Did you forget to indent the block inside the {0}?" - "".format(name, key, key2) + f"Cannot have two {name}s in one item. Key '{key}' overrides '{key2}'! " + f"Did you forget to indent the block inside the {key}?" ) if value[key] is None: diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index c45d0969e1..e89f64d14c 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -624,8 +624,7 @@ class EsphomeCore: expression = statement(expression) if not isinstance(expression, Statement): raise ValueError( - "Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression)) + f"Add '{expression}' must be expression or statement, not {type(expression)}" ) self.main_statements.append(expression) @@ -639,8 +638,7 @@ class EsphomeCore: expression = statement(expression) if not isinstance(expression, Statement): raise ValueError( - "Add '{}' must be expression or statement, not {}" - "".format(expression, type(expression)) + f"Add '{expression}' must be expression or statement, not {type(expression)}" ) self.global_statements.append(expression) _LOGGER.debug("Adding global: %s", expression) @@ -649,8 +647,7 @@ class EsphomeCore: def add_library(self, library): if not isinstance(library, Library): raise ValueError( - "Library {} must be instance of Library, not {}" - "".format(library, type(library)) + f"Library {library} must be instance of Library, not {type(library)}" ) for other in self.libraries[:]: if other.name != library.name or other.name is None or library.name is None: @@ -660,9 +657,8 @@ class EsphomeCore: # Other is using a/the same repository, takes precendence break raise ValueError( - "Adding named Library with repository failed! Libraries {} and {} " + f"Adding named Library with repository failed! Libraries {library} and {other} " "requested with conflicting repositories!" - "".format(library, other) ) if library.repository is not None: @@ -681,9 +677,8 @@ class EsphomeCore: break raise ValueError( - "Version pinning failed! Libraries {} and {} " + f"Version pinning failed! Libraries {library} and {other} " "requested with conflicting versions!" - "".format(library, other) ) else: _LOGGER.debug("Adding library: %s", library) @@ -702,8 +697,7 @@ class EsphomeCore: pass else: raise ValueError( - "Define {} must be string or Define, not {}" - "".format(define, type(define)) + f"Define {define} must be string or Define, not {type(define)}" ) self.defines.add(define) _LOGGER.debug("Adding define: %s", define) diff --git a/esphome/core/config.py b/esphome/core/config.py index 45b8809f1e..222a775ee6 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -61,9 +61,7 @@ def validate_board(value: str): if value not in boardlist: raise cv.Invalid( - "Could not find board '{}'. Valid boards are {}".format( - value, ", ".join(sorted(boardlist)) - ) + f"Could not find board '{value}'. Valid boards are {', '.join(sorted(boardlist))}" ) return value @@ -102,9 +100,7 @@ def validate_arduino_version(value): and value_ not in PLATFORMIO_ESP8266_LUT ): raise cv.Invalid( - "Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif8266@".format(value) + f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif8266@" ) if value_ in PLATFORMIO_ESP8266_LUT: return PLATFORMIO_ESP8266_LUT[value_] @@ -115,9 +111,7 @@ def validate_arduino_version(value): and value_ not in PLATFORMIO_ESP32_LUT ): raise cv.Invalid( - "Unfortunately the arduino framework version '{}' is unsupported " - "at this time. You can override this by manually using " - "espressif32@".format(value) + f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif32@" ) if value_ in PLATFORMIO_ESP32_LUT: return PLATFORMIO_ESP32_LUT[value_] @@ -141,8 +135,7 @@ def valid_include(value): _, ext = os.path.splitext(value) if ext not in VALID_INCLUDE_EXTS: raise cv.Invalid( - "Include has invalid file extension {} - valid extensions are {}" - "".format(ext, ", ".join(VALID_INCLUDE_EXTS)) + f"Include has invalid file extension {ext} - valid extensions are {', '.join(VALID_INCLUDE_EXTS)}" ) return value diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index eda378e5eb..0442e2633b 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -182,7 +182,7 @@ class ArrayInitializer(Expression): cpp += f" {arg},\n" cpp += "}" else: - cpp = "{" + ", ".join(str(arg) for arg in self.args) + "}" + cpp = f"{{{', '.join(str(arg) for arg in self.args)}}}" return cpp @@ -348,13 +348,11 @@ def safe_exp(obj: SafeExpType) -> Expression: return float_ if isinstance(obj, ID): raise ValueError( - "Object {} is an ID. Did you forget to register the variable?" - "".format(obj) + f"Object {obj} is an ID. Did you forget to register the variable?" ) if inspect.isgenerator(obj): raise ValueError( - "Object {} is a coroutine. Did you forget to await the expression with " - "'await'?".format(obj) + f"Object {obj} is a coroutine. Did you forget to await the expression with 'await'?" ) raise ValueError("Object is not an expression", obj) @@ -703,7 +701,7 @@ class MockObj(Expression): return str(self.base) def __repr__(self): - return "MockObj<{}>".format(str(self.base)) + return f"MockObj<{str(self.base)}>" @property def _(self) -> "MockObj": @@ -761,7 +759,7 @@ class MockObjEnum(MockObj): self._is_class = kwargs.pop("is_class") base = kwargs.pop("base") if self._is_class: - base = base + "::" + self._enum + base = f"{base}::{self._enum}" kwargs["op"] = "::" kwargs["base"] = base MockObj.__init__(self, *args, **kwargs) diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 7912e4ae06..33fb485c2b 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -52,9 +52,7 @@ async def register_component(var, config): id_ = str(var.base) if id_ not in CORE.component_ids: raise ValueError( - "Component ID {} was not declared to inherit from Component, " - "or was registered twice. Please create a bug report with your " - "configuration.".format(id_) + f"Component ID {id_} was not declared to inherit from Component, or was registered twice. Please create a bug report with your configuration." ) CORE.component_ids.remove(id_) if CONF_SETUP_PRIORITY in config: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 97f9d60693..38689675af 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -368,7 +368,7 @@ class WizardRequestHandler(BaseHandler): if k in ("name", "platform", "board", "ssid", "psk", "password") } kwargs["ota_password"] = secrets.token_hex(16) - destination = settings.rel_path(kwargs["name"] + ".yaml") + destination = settings.rel_path(f"{kwargs['name']}.yaml") wizard.wizard_write(path=destination, **kwargs) self.set_status(200) self.finish() @@ -512,7 +512,7 @@ class MDNSStatusThread(threading.Thread): while not STOP_EVENT.is_set(): entries = _list_dashboard_entries() stat.request_query( - {entry.filename: entry.name + ".local." for entry in entries} + {entry.filename: f"{entry.name}.local." for entry in entries} ) PING_REQUEST.wait() @@ -795,27 +795,27 @@ def make_app(debug=get_bool_env(ENV_DEV)): rel = settings.relative_url app = tornado.web.Application( [ - (rel + "", MainRequestHandler), - (rel + "login", LoginHandler), - (rel + "logout", LogoutHandler), - (rel + "logs", EsphomeLogsHandler), - (rel + "upload", EsphomeUploadHandler), - (rel + "compile", EsphomeCompileHandler), - (rel + "validate", EsphomeValidateHandler), - (rel + "clean-mqtt", EsphomeCleanMqttHandler), - (rel + "clean", EsphomeCleanHandler), - (rel + "vscode", EsphomeVscodeHandler), - (rel + "ace", EsphomeAceEditorHandler), - (rel + "update-all", EsphomeUpdateAllHandler), - (rel + "info", InfoRequestHandler), - (rel + "edit", EditRequestHandler), - (rel + "download.bin", DownloadBinaryRequestHandler), - (rel + "serial-ports", SerialPortRequestHandler), - (rel + "ping", PingRequestHandler), - (rel + "delete", DeleteRequestHandler), - (rel + "undo-delete", UndoDeleteRequestHandler), - (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}), + (f"{rel}", MainRequestHandler), + (f"{rel}login", LoginHandler), + (f"{rel}logout", LogoutHandler), + (f"{rel}logs", EsphomeLogsHandler), + (f"{rel}upload", EsphomeUploadHandler), + (f"{rel}compile", EsphomeCompileHandler), + (f"{rel}validate", EsphomeValidateHandler), + (f"{rel}clean-mqtt", EsphomeCleanMqttHandler), + (f"{rel}clean", EsphomeCleanHandler), + (f"{rel}vscode", EsphomeVscodeHandler), + (f"{rel}ace", EsphomeAceEditorHandler), + (f"{rel}update-all", EsphomeUpdateAllHandler), + (f"{rel}info", InfoRequestHandler), + (f"{rel}edit", EditRequestHandler), + (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}serial-ports", SerialPortRequestHandler), + (f"{rel}ping", PingRequestHandler), + (f"{rel}delete", DeleteRequestHandler), + (f"{rel}undo-delete", UndoDeleteRequestHandler), + (f"{rel}wizard.html", WizardRequestHandler), + (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), ], **app_settings, ) diff --git a/esphome/espota2.py b/esphome/espota2.py index 351f6feda9..f8a2fab94c 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -52,9 +52,7 @@ class ProgressBar: return self.last_progress = new_progress block = int(round(bar_length * progress)) - text = "\rUploading: [{0}] {1}% {2}".format( - "=" * block + " " * (bar_length - block), new_progress, status - ) + text = f"\rUploading: [{'=' * block + ' ' * (bar_length - block)}] {new_progress}% {status}" sys.stderr.write(text) sys.stderr.flush() @@ -154,7 +152,7 @@ def check_error(data, expect): if not isinstance(expect, (list, tuple)): expect = [expect] if dat not in expect: - raise OTAError("Unexpected response from ESP: 0x{:02X}".format(data[0])) + raise OTAError(f"Unexpected response from ESP: 0x{data[0]:02X}") def send_check(sock, data, msg): diff --git a/esphome/helpers.py b/esphome/helpers.py index a1cb4367c5..3420b2cc12 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -54,7 +54,7 @@ def cpp_string_escape(string, encoding="utf-8"): result += f"\\{character:03o}" else: result += chr(character) - return '"' + result + '"' + return f'"{result}"' def run_system_command(*args): @@ -107,7 +107,7 @@ def _resolve_with_zeroconf(host): "host network mode?" ) from err try: - info = zc.resolve_host(host + ".") + info = zc.resolve_host(f"{host}.") except Exception as err: raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err finally: @@ -136,9 +136,7 @@ def resolve_ip_address(host): return socket.gethostbyname(host) except OSError as err: errs.append(str(err)) - raise EsphomeError( - "Error resolving IP address: {}" "".format(", ".join(errs)) - ) from err + raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err def get_bool_env(var, default=False): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 9be87b5c5d..07602e8ced 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -104,9 +104,9 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None): if CONF_LOG_TOPIC in conf: topic = config[CONF_MQTT][CONF_LOG_TOPIC][CONF_TOPIC] elif CONF_TOPIC_PREFIX in config[CONF_MQTT]: - topic = config[CONF_MQTT][CONF_TOPIC_PREFIX] + "/debug" + topic = f"{config[CONF_MQTT][CONF_TOPIC_PREFIX]}/debug" else: - topic = config[CONF_ESPHOME][CONF_NAME] + "/debug" + topic = f"{config[CONF_ESPHOME][CONF_NAME]}/debug" else: _LOGGER.error("MQTT isn't setup, can't start MQTT logs") return 1 @@ -158,9 +158,8 @@ def get_fingerprint(config): sha1 = hashlib.sha1(cert_der).hexdigest() - safe_print("SHA1 Fingerprint: " + color(Fore.CYAN, sha1)) + safe_print(f"SHA1 Fingerprint: {color(Fore.CYAN, sha1)}") safe_print( - "Copy the string above into mqtt.ssl_fingerprints section of {}" - "".format(CORE.config_path) + f"Copy the string above into mqtt.ssl_fingerprints section of {CORE.config_path}" ) return 0 diff --git a/esphome/pins.py b/esphome/pins.py index ff4ed9d9c1..c717424ff3 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -77,8 +77,7 @@ def validate_gpio_pin(value): raise cv.Invalid(f"ESP32-C3: Invalid pin number: {value}") if value in _ESP32C3_SDIO_PINS: raise cv.Invalid( - "This pin cannot be used on ESP32-C3s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + f"This pin cannot be used on ESP32-C3s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) return value if CORE.is_esp32: @@ -86,8 +85,7 @@ def validate_gpio_pin(value): raise cv.Invalid(f"ESP32: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: raise cv.Invalid( - "This pin cannot be used on ESP32s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) if 9 <= value <= 10: _LOGGER.warning( @@ -105,8 +103,7 @@ def validate_gpio_pin(value): raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") if value in _ESP_SDIO_PINS: raise cv.Invalid( - "This pin cannot be used on ESP8266s and is already used by " - "the flash interface (function: {})".format(_ESP_SDIO_PINS[value]) + f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" ) if 9 <= value <= 10: _LOGGER.warning( @@ -144,8 +141,7 @@ def output_pin(value): if CORE.is_esp32: if 34 <= value <= 39: raise cv.Invalid( - "ESP32: GPIO{} (34-39) can only be used as an " - "input pin.".format(value) + f"ESP32: GPIO{value} (34-39) can only be used as an input pin." ) return value if CORE.is_esp8266: @@ -278,8 +274,7 @@ def validate_has_interrupt(value): if CORE.is_esp8266: if value[CONF_NUMBER] >= 16: raise cv.Invalid( - "Pins GPIO16 and GPIO17 do not support interrupts and cannot be used " - "here, got {}".format(value[CONF_NUMBER]) + f"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used here, got {value[CONF_NUMBER]}" ) return value diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 3d7e9a6d48..6506a44e88 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -39,7 +39,7 @@ def patch_structhash(): command.clean_build_dir = patched_clean_build_dir -IGNORE_LIB_WARNINGS = r"(?:" + "|".join(["Hash", "Update"]) + r")" +IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})" FILTER_PLATFORMIO_LINES = [ r"Verbose mode can be enabled via `-v, --verbose` option.*", r"CONFIGURATION: https://docs.platformio.org/.*", @@ -48,13 +48,9 @@ FILTER_PLATFORMIO_LINES = [ r"PACKAGES: .*", r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", - r"Looking for " + IGNORE_LIB_WARNINGS + r" library in registry", - r"Warning! Library `.*'" - + IGNORE_LIB_WARNINGS - + r".*` has not been found in PlatformIO Registry.", - r"You can ignore this message, if `.*" - + IGNORE_LIB_WARNINGS - + r".*` is a built-in library.*", + f"Looking for {IGNORE_LIB_WARNINGS} library in registry", + f"Warning! Library `.*'{IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.", + f"You can ignore this message, if `.*{IGNORE_LIB_WARNINGS}.*` is a built-in library.*", r"Scanning dependencies...", r"Found \d+ compatible libraries", r"Memory Usage -> http://bit.ly/pio-memory-usage", @@ -296,6 +292,6 @@ class IDEData: # Windows if cc_path.endswith(".exe"): - return cc_path[:-7] + "addr2line.exe" + return f"{cc_path[:-7]}addr2line.exe" - return cc_path[:-3] + "addr2line" + return f"{cc_path[:-3]}addr2line" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index c0fbc6edf7..517bb508ba 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -94,7 +94,7 @@ class StorageJSON: } def to_json(self): - return json.dumps(self.as_dict(), indent=2) + "\n" + return f"{json.dumps(self.as_dict(), indent=2)}\n" def save(self, path): write_file_if_changed(path, self.to_json()) @@ -214,7 +214,7 @@ class EsphomeStorageJSON: self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S") def to_json(self): # type: () -> dict - return json.dumps(self.as_dict(), indent=2) + "\n" + return f"{json.dumps(self.as_dict(), indent=2)}\n" def save(self, path): # type: (str) -> None write_file_if_changed(path, self.to_json()) diff --git a/esphome/wizard.py b/esphome/wizard.py index 3f989dd93d..abb17e3119 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -74,19 +74,20 @@ def wizard_file(**kwargs): # Configure API if "password" in kwargs: - config += ' password: "{0}"\n'.format(kwargs["password"]) + config += f" password: \"{kwargs['password']}\"\n" # Configure OTA config += "\nota:\n" if "ota_password" in kwargs: - config += ' password: "{0}"'.format(kwargs["ota_password"]) + config += f" password: \"{kwargs['ota_password']}\"" elif "password" in kwargs: - config += ' password: "{0}"'.format(kwargs["password"]) + config += f" password: \"{kwargs['password']}\"" # Configuring wifi config += "\n\nwifi:\n" if "ssid" in kwargs: + # pylint: disable=consider-using-f-string config += """ ssid: "{ssid}" password: "{psk}" """.format( @@ -99,6 +100,7 @@ def wizard_file(**kwargs): networks: """ + # pylint: disable=consider-using-f-string config += """ # Enable fallback hotspot (captive portal) in case wifi connection fails ap: @@ -126,7 +128,7 @@ def wizard_write(path, **kwargs): platform = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, name + ".local", platform, board) + storage = StorageJSON.from_wizard(name, f"{name}.local", platform, board) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) @@ -168,14 +170,12 @@ def strip_accents(value): def wizard(path): if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( - "Please make your configuration file {} have the extension .yaml or .yml" - "".format(color(Fore.CYAN, path)) + f"Please make your configuration file {color(Fore.CYAN, path)} have the extension .yaml or .yml" ) return 1 if os.path.exists(path): safe_print( - "Uh oh, it seems like {} already exists, please delete that file first " - "or chose another configuration file.".format(color(Fore.CYAN, path)) + f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." ) return 2 safe_print("Hi there!") @@ -191,17 +191,13 @@ def wizard(path): sleep(3.0) safe_print() safe_print_step(1, CORE_BIG) - safe_print( - "First up, please choose a " + color(Fore.GREEN, "name") + " for your node." - ) + safe_print(f"First up, please choose a {color(Fore.GREEN, 'name')} for your node.") safe_print( "It should be a unique name that can be used to identify the device later." ) sleep(1) safe_print( - "For example, I like calling the node in my living room {}.".format( - color(Fore.BOLD_WHITE, "livingroom") - ) + f"For example, I like calling the node in my living room {color(Fore.BOLD_WHITE, 'livingroom')}." ) safe_print() sleep(1) @@ -222,13 +218,11 @@ def wizard(path): name = strip_accents(name).lower().replace(" ", "-") name = strip_accents(name).lower().replace("_", "-") name = "".join(c for c in name if c in ALLOWED_NAME_CHARS) - safe_print( - 'Shall I use "{}" as the name instead?'.format(color(Fore.CYAN, name)) - ) + safe_print(f'Shall I use "{color(Fore.CYAN, name)}" as the name instead?') sleep(0.5) name = default_input("(name [{}]): ", name) - safe_print('Great! Your node is now called "{}".'.format(color(Fore.CYAN, name))) + safe_print(f'Great! Your node is now called "{color(Fore.CYAN, name)}".') sleep(1) safe_print_step(2, ESP_BIG) safe_print( @@ -236,11 +230,7 @@ def wizard(path): "firmwares for it." ) safe_print( - "Are you using an " - + color(Fore.GREEN, "ESP32") - + " or " - + color(Fore.GREEN, "ESP8266") - + " platform? (Choose ESP8266 for Sonoff devices)" + f"Are you using an {color(Fore.GREEN, 'ESP32')} or {color(Fore.GREEN, 'ESP8266')} platform? (Choose ESP8266 for Sonoff devices)" ) while True: sleep(0.5) @@ -252,12 +242,9 @@ def wizard(path): break except vol.Invalid: safe_print( - "Unfortunately, I can't find an espressif microcontroller called " - '"{}". Please try again.'.format(platform) + f'Unfortunately, I can\'t find an espressif microcontroller called "{platform}". Please try again.' ) - safe_print( - "Thanks! You've chosen {} as your platform.".format(color(Fore.CYAN, platform)) - ) + safe_print(f"Thanks! You've chosen {color(Fore.CYAN, platform)} as your platform.") safe_print() sleep(1) @@ -270,24 +257,20 @@ def wizard(path): "http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" ) - safe_print( - "Next, I need to know what " + color(Fore.GREEN, "board") + " you're using." - ) + safe_print(f"Next, I need to know what {color(Fore.GREEN, 'board')} you're using.") sleep(0.5) - safe_print( - "Please go to {} and choose a board.".format(color(Fore.GREEN, board_link)) - ) + safe_print(f"Please go to {color(Fore.GREEN, board_link)} and choose a board.") if platform == "ESP32": - safe_print("(Type " + color(Fore.GREEN, "esp01_1m") + " for Sonoff devices)") + safe_print(f"(Type {color(Fore.GREEN, 'esp01_1m')} for Sonoff devices)") safe_print() # Don't sleep because user needs to copy link if platform == "ESP32": - safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcu-32s"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") boards = list(ESP32_BOARD_PINS.keys()) else: - safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcuv2"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") boards = list(ESP8266_BOARD_PINS.keys()) - safe_print("Options: {}".format(", ".join(sorted(boards)))) + safe_print(f"Options: {', '.join(sorted(boards))}") while True: board = input(color(Fore.BOLD_WHITE, "(board): ")) @@ -302,9 +285,7 @@ def wizard(path): sleep(0.25) safe_print() - safe_print( - "Way to go! You've chosen {} as your board.".format(color(Fore.CYAN, board)) - ) + safe_print(f"Way to go! You've chosen {color(Fore.CYAN, board)} as your board.") safe_print() sleep(1) @@ -313,12 +294,10 @@ def wizard(path): safe_print() sleep(1) safe_print( - "First, what's the " - + color(Fore.GREEN, "SSID") - + f" (the name) of the WiFi network {name} should connect to?" + f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?" ) sleep(1.5) - safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "Abraham Linksys"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'Abraham Linksys')}\".") while True: ssid = input(color(Fore.BOLD_WHITE, "(ssid): ")) try: @@ -328,27 +307,23 @@ def wizard(path): safe_print( color( Fore.RED, - 'Unfortunately, "{}" doesn\'t seem to be a valid SSID. ' - "Please try again.".format(ssid), + f'Unfortunately, "{ssid}" doesn\'t seem to be a valid SSID. Please try again.', ) ) safe_print() sleep(1) safe_print( - 'Thank you very much! You\'ve just chosen "{}" as your SSID.' - "".format(color(Fore.CYAN, ssid)) + f'Thank you very much! You\'ve just chosen "{color(Fore.CYAN, ssid)}" as your SSID.' ) safe_print() sleep(0.75) safe_print( - "Now please state the " - + color(Fore.GREEN, "password") - + " of the WiFi network so that I can connect to it (Leave empty for no password)" + f"Now please state the {color(Fore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)" ) safe_print() - safe_print('For example "{}"'.format(color(Fore.BOLD_WHITE, "PASSWORD42"))) + safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'PASSWORD42')}\"") sleep(0.5) psk = input(color(Fore.BOLD_WHITE, "(PSK): ")) safe_print( @@ -362,8 +337,7 @@ def wizard(path): "(over the air) and integrates into Home Assistant with a native API." ) safe_print( - "This can be insecure if you do not trust the WiFi network. Do you want to set " - "a " + color(Fore.GREEN, "password") + " for connecting to this ESP?" + f"This can be insecure if you do not trust the WiFi network. Do you want to set a {color(Fore.GREEN, 'password')} for connecting to this ESP?" ) safe_print() sleep(0.25) diff --git a/esphome/writer.py b/esphome/writer.py index 09ed284173..19953916c7 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -96,7 +96,7 @@ def get_include_text(): includes = "\n".join(includes) if not includes: continue - include_text += includes + "\n" + include_text += f"{includes}\n" return include_text @@ -134,7 +134,7 @@ def migrate_src_version_0_to_1(): content, count = replace_file_content( content, r'#include "esphomelib/application.h"', - CPP_INCLUDE_BEGIN + "\n" + CPP_INCLUDE_END, + f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}", ) if count == 0: _LOGGER.error( @@ -322,7 +322,7 @@ def write_platformio_ini(content): ) else: content_format = INI_BASE_FORMAT - full_file = content_format[0] + INI_AUTO_GENERATE_BEGIN + "\n" + content + full_file = f"{content_format[0] + INI_AUTO_GENERATE_BEGIN}\n{content}" full_file += INI_AUTO_GENERATE_END + content_format[1] write_file_if_changed(path, full_file) @@ -444,9 +444,9 @@ def write_cpp(code_s): global_s = '#include "esphome.h"\n' global_s += CORE.cpp_global_section - full_file = code_format[0] + CPP_INCLUDE_BEGIN + "\n" + global_s + CPP_INCLUDE_END + full_file = f"{code_format[0] + CPP_INCLUDE_BEGIN}\n{global_s}{CPP_INCLUDE_END}" full_file += ( - code_format[1] + CPP_AUTO_GENERATE_BEGIN + "\n" + code_s + CPP_AUTO_GENERATE_END + f"{code_format[1] + CPP_AUTO_GENERATE_BEGIN}\n{code_s}{CPP_AUTO_GENERATE_END}" ) full_file += code_format[2] write_file_if_changed(path, full_file) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 10417e6de4..bdadbbd43a 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -183,9 +183,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors raise yaml.constructor.ConstructorError( "While constructing a mapping", node.start_mark, - "Expected a mapping for merging, but found {}".format( - type(item) - ), + f"Expected a mapping for merging, but found {type(item)}", value_node.start_mark, ) merge_pairs.extend(item.items()) @@ -193,8 +191,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors raise yaml.constructor.ConstructorError( "While constructing a mapping", node.start_mark, - "Expected a mapping or list of mappings for merging, " - "but found {}".format(type(value)), + f"Expected a mapping or list of mappings for merging, but found {type(value)}", value_node.start_mark, ) diff --git a/requirements_test.txt b/requirements_test.txt index bb735193f0..1d09bb6388 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.10.2 +pylint==2.11.1 flake8==3.9.2 black==21.9b0 pexpect==4.8.0 From 53bd197c44882a4ce1fd6d440ad2458306612ffd Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Sun, 19 Sep 2021 23:17:43 +0200 Subject: [PATCH 253/285] Add eco mode to tuya climate component (#1860) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tuya/climate/__init__.py | 17 +++++++ .../components/tuya/climate/tuya_climate.cpp | 47 +++++++++++++++++-- .../components/tuya/climate/tuya_climate.h | 13 +++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 471a8146e1..275a87edd3 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -23,6 +23,8 @@ CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" CONF_TEMPERATURE_MULTIPLIER = "temperature_multiplier" CONF_CURRENT_TEMPERATURE_MULTIPLIER = "current_temperature_multiplier" CONF_TARGET_TEMPERATURE_MULTIPLIER = "target_temperature_multiplier" +CONF_ECO_DATAPOINT = "eco_datapoint" +CONF_ECO_TEMPERATURE = "eco_temperature" TuyaClimate = tuya_ns.class_("TuyaClimate", climate.Climate, cg.Component) @@ -90,6 +92,14 @@ def validate_active_state_values(value): return value +def validate_eco_values(value): + if CONF_ECO_TEMPERATURE in value and CONF_ECO_DATAPOINT not in value: + raise cv.Invalid( + f"{CONF_ECO_DATAPOINT} required if using {CONF_ECO_TEMPERATURE}" + ) + return value + + CONFIG_SCHEMA = cv.All( climate.CLIMATE_SCHEMA.extend( { @@ -108,6 +118,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_CURRENT_TEMPERATURE_MULTIPLIER): cv.positive_float, cv.Optional(CONF_TARGET_TEMPERATURE_MULTIPLIER): cv.positive_float, + cv.Optional(CONF_ECO_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_ECO_TEMPERATURE): cv.temperature, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), @@ -115,6 +127,7 @@ CONFIG_SCHEMA = cv.All( validate_active_state_values, cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_HEATING_STATE_PIN), cv.has_at_most_one_key(CONF_ACTIVE_STATE_DATAPOINT, CONF_COOLING_STATE_PIN), + validate_eco_values, ) @@ -179,3 +192,7 @@ async def to_code(config): config[CONF_TARGET_TEMPERATURE_MULTIPLIER] ) ) + if CONF_ECO_DATAPOINT in config: + cg.add(var.set_eco_id(config[CONF_ECO_DATAPOINT])) + if CONF_ECO_TEMPERATURE in config: + cg.add(var.set_eco_temperature(config[CONF_ECO_TEMPERATURE])) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index ae4424e438..293bfb4f88 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -43,8 +43,9 @@ void TuyaClimate::setup() { } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) { - this->target_temperature = datapoint.value_int * this->target_temperature_multiplier_; - ESP_LOGV(TAG, "MCU reported target temperature is: %.1f", this->target_temperature); + this->manual_temperature_ = datapoint.value_int * this->target_temperature_multiplier_; + ESP_LOGV(TAG, "MCU reported manual target temperature is: %.1f", this->manual_temperature_); + this->compute_target_temperature_(); this->compute_state_(); this->publish_state(); }); @@ -57,6 +58,15 @@ void TuyaClimate::setup() { this->publish_state(); }); } + if (this->eco_id_.has_value()) { + this->parent_->register_listener(*this->eco_id_, [this](const TuyaDatapoint &datapoint) { + this->eco_ = datapoint.value_bool; + ESP_LOGV(TAG, "MCU reported eco is: %s", ONOFF(this->eco_)); + this->compute_preset_(); + this->compute_target_temperature_(); + this->publish_state(); + }); + } } void TuyaClimate::loop() { @@ -100,16 +110,29 @@ void TuyaClimate::control(const climate::ClimateCall &call) { this->parent_->set_integer_datapoint_value(*this->target_temperature_id_, (int) (target_temperature / this->target_temperature_multiplier_)); } + + if (call.get_preset().has_value()) { + const climate::ClimatePreset preset = *call.get_preset(); + if (this->eco_id_.has_value()) { + const bool eco = preset == climate::CLIMATE_PRESET_ECO; + ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco)); + this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco); + } + } } climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); + traits.set_supports_action(true); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); if (supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); if (supports_cool_) traits.add_supported_mode(climate::CLIMATE_MODE_COOL); - traits.set_supports_action(true); + if (this->eco_id_.has_value()) { + traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + } return traits; } @@ -125,6 +148,24 @@ void TuyaClimate::dump_config() { ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); LOG_PIN(" Heating State Pin: ", this->heating_state_pin_); LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_); + if (this->eco_id_.has_value()) + ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_); +} + +void TuyaClimate::compute_preset_() { + if (this->eco_) { + this->preset = climate::CLIMATE_PRESET_ECO; + } else { + this->preset = climate::CLIMATE_PRESET_NONE; + } +} + +void TuyaClimate::compute_target_temperature_() { + if (this->eco_ && this->eco_temperature_.has_value()) { + this->target_temperature = *this->eco_temperature_; + } else { + this->target_temperature = this->manual_temperature_; + } } void TuyaClimate::compute_state_() { diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index f1a0c13a77..ec19d05308 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -32,15 +32,24 @@ class TuyaClimate : public climate::Climate, public Component { void set_target_temperature_multiplier(float temperature_multiplier) { this->target_temperature_multiplier_ = temperature_multiplier; } + void set_eco_id(uint8_t eco_id) { this->eco_id_ = eco_id; } + void set_eco_temperature(float eco_temperature) { this->eco_temperature_ = eco_temperature; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } protected: /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; + /// Return the traits of this controller. climate::ClimateTraits traits() override; + /// Re-compute the active preset of this climate controller. + void compute_preset_(); + + /// Re-compute the target temperature of this climate controller. + void compute_target_temperature_(); + /// Re-compute the state of this climate controller. void compute_state_(); @@ -61,9 +70,13 @@ class TuyaClimate : public climate::Climate, public Component { float current_temperature_multiplier_{1.0f}; float target_temperature_multiplier_{1.0f}; float hysteresis_{1.0f}; + optional eco_id_{}; + optional eco_temperature_{}; uint8_t active_state_; bool heating_state_{false}; bool cooling_state_{false}; + float manual_temperature_; + bool eco_; }; } // namespace tuya From c60c61820453a50557f21f79e2d6196be7ff7050 Mon Sep 17 00:00:00 2001 From: Luca Gugelmann <69755789+lgugelmann@users.noreply.github.com> Date: Sun, 19 Sep 2021 23:19:20 +0200 Subject: [PATCH 254/285] Fix SPIDevice::write_byte16 to actually take a 16 bit argument (#2345) --- esphome/components/spi/spi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index a4a2e11def..eb8f9ce7ce 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -246,7 +246,7 @@ class SPIDevice { return this->parent_->template write_byte(data); } - void write_byte16(uint8_t data) { + void write_byte16(uint16_t data) { return this->parent_->template write_byte16(data); } From a9908982565d1f386595dd29c2ef2d46fd3089ce Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 00:33:10 +0200 Subject: [PATCH 255/285] Add readv and writev for more efficient API packets (#2342) --- esphome/components/api/api_connection.cpp | 29 ++-- esphome/components/api/api_frame_helper.cpp | 127 ++++++++++-------- esphome/components/api/api_frame_helper.h | 5 +- .../components/socket/bsd_sockets_impl.cpp | 48 +++++++ esphome/components/socket/headers.h | 6 + .../components/socket/lwip_raw_tcp_impl.cpp | 87 +++++++++--- esphome/components/socket/socket.h | 2 + 7 files changed, 220 insertions(+), 84 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 31a530c04d..0fa4ca6397 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -702,15 +702,7 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 - bool success = this->send_buffer(buffer, 29); - if (!success) { - buffer = this->create_buffer(); - // bool send_failed = 4; - buffer.encode_bool(4, true); - return this->send_buffer(buffer, 29); - } else { - return true; - } + return this->send_buffer(buffer, 29); } HelloResponse APIConnection::hello(const HelloRequest &msg) { @@ -783,8 +775,23 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) { if (this->remove_) return false; - if (!this->helper_->can_write_without_blocking()) - return false; + if (!this->helper_->can_write_without_blocking()) { + delay(0); + APIError err = helper_->loop(); + if (err != APIError::OK) { + on_fatal_error(); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + return false; + } + if (!this->helper_->can_write_without_blocking()) { + // SubscribeLogsResponse + if (message_type != 29) { + ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); + } + delay(0); + return false; + } + } APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size()); if (err == APIError::WOULD_BLOCK) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index e68831e594..15014b7937 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -125,13 +125,6 @@ APIError APINoiseFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } // init prologue prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT)); @@ -494,12 +487,13 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload size_t total_len = 3 + mbuf.size; tmpbuf[1] = (uint8_t)(mbuf.size >> 8); tmpbuf[2] = (uint8_t) mbuf.size; + + struct iovec iov; + iov.iov_base = &tmpbuf[0]; + iov.iov_len = total_len; + // write raw to not have two packets sent if NAGLE disabled - aerr = write_raw_(&tmpbuf[0], total_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + return write_raw_(&iov, 1); } APIError APINoiseFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -526,16 +520,19 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -546,41 +543,56 @@ APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent return APIError::OK; } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) { - APIError aerr; - uint8_t header[3]; header[0] = 0x01; // indicator header[1] = (uint8_t)(len >> 8); header[2] = (uint8_t) len; - aerr = write_raw_(header, 3); - if (aerr != APIError::OK) - return aerr; - aerr = write_raw_(data, len); - return aerr; + struct iovec iov[2]; + iov[0].iov_base = header; + iov[0].iov_len = 3; + iov[1].iov_base = const_cast(data); + iov[1].iov_len = len; + + return write_raw_(iov, 2); } /** Initiate the data structures for the handshake. @@ -709,13 +721,6 @@ APIError APIPlaintextFrameHelper::init() { HELPER_LOG("Setting nonblocking failed with errno %d", errno); return APIError::TCP_NONBLOCKING_FAILED; } - int enable = 1; - err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); - if (err != 0) { - state_ = State::FAILED; - HELPER_LOG("Setting nodelay failed with errno %d", errno); - return APIError::TCP_NODELAY_FAILED; - } state_ = State::DATA; return APIError::OK; @@ -863,15 +868,13 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay ProtoVarInt(payload_len).encode(header); ProtoVarInt(type).encode(header); - aerr = write_raw_(&header[0], header.size()); - if (aerr != APIError::OK) { - return aerr; - } - aerr = write_raw_(payload, payload_len); - if (aerr != APIError::OK) { - return aerr; - } - return APIError::OK; + struct iovec iov[2]; + iov[0].iov_base = &header[0]; + iov[0].iov_len = header.size(); + iov[1].iov_base = const_cast(payload); + iov[1].iov_len = payload_len; + + return write_raw_(iov, 2); } APIError APIPlaintextFrameHelper::try_send_tx_buf_() { // try send from tx_buf @@ -896,16 +899,19 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { * @param data The data to write * @param len The length of data */ -APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { - if (len == 0) +APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { + if (iovcnt == 0) return APIError::OK; int err; APIError aerr; - // uncomment for even more debugging + size_t total_write_len = 0; + for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif + total_write_len += iov[i].iov_len; + } if (!tx_buf_.empty()) { // try to empty tx_buf_ first @@ -916,23 +922,38 @@ APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) { if (!tx_buf_.empty()) { // tx buf not empty, can't write now because then stream would be inconsistent - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } - ssize_t sent = socket_->write(data, len); + ssize_t sent = socket_->writev(iov, iovcnt); if (is_would_block(sent)) { // operation would block, add buffer to tx_buf - tx_buf_.insert(tx_buf_.end(), data, data + len); + for (int i = 0; i < iovcnt; i++) { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base), + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + } return APIError::OK; } else if (sent == -1) { // an error occured state_ = State::FAILED; HELPER_LOG("Socket write failed with errno %d", errno); return APIError::SOCKET_WRITE_FAILED; - } else if (sent != len) { + } else if (sent != total_write_len) { // partially sent, add end to tx_buf - tx_buf_.insert(tx_buf_.end(), data + sent, data + len); + size_t to_consume = sent; + for (int i = 0; i < iovcnt; i++) { + if (to_consume >= iov[i].iov_len) { + to_consume -= iov[i].iov_len; + } else { + tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume, + reinterpret_cast(iov[i].iov_base) + iov[i].iov_len); + to_consume = 0; + } + } return APIError::OK; } // fully sent diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a9a653cf4f..44df629b2f 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -58,6 +58,7 @@ const char *api_error_to_str(APIError err); class APIFrameHelper { public: + virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; @@ -96,7 +97,7 @@ class APINoiseFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); APIError write_frame_(const uint8_t *data, size_t len); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const std::string &reason); @@ -154,7 +155,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { APIError try_read_frame_(ParsedFrame *frame); APIError try_send_tx_buf_(); - APIError write_raw_(const uint8_t *data, size_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt); std::unique_ptr socket_; diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 6fb00ce22d..aa1bcd3b3c 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -6,6 +6,10 @@ #include +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif + namespace esphome { namespace socket { @@ -76,7 +80,51 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have readv, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::readv(fd_, iov, iovcnt); +#endif + } ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 + // esp-idf v3 doesn't have writev, emulate it + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = + this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); + if (err == -1) { + if (ret != 0) + // if we already wrote some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; +#else + return ::writev(fd_, iov, iovcnt); +#endif + } int setblocking(bool blocking) override { int fl = ::fcntl(fd_, F_GETFL, 0); if (blocking) { diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index da710b760e..fbe8f929a0 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -81,6 +81,11 @@ struct sockaddr_storage { }; typedef uint32_t socklen_t; +struct iovec { + void *iov_base; + size_t iov_len; +}; + #ifdef ARDUINO_ARCH_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY @@ -104,6 +109,7 @@ typedef uint32_t socklen_t; #include #include #include +#include #include #include #include diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 2147e36632..366f0972ef 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -371,7 +371,23 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t write(const void *buf, size_t len) override { + ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t ret = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (ret != 0) + // if we already read some don't return an error + break; + return err; + } + ret += err; + if (err != iov[i].iov_len) + break; + } + return ret; + } + ssize_t internal_write(const void *buf, size_t len) { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -400,25 +416,60 @@ class LWIPRawImpl : public Socket { errno = ECONNRESET; return -1; } - if (tcp_nagle_disabled(pcb_)) { - LWIP_LOG("tcp_output(%p)", pcb_); - err = tcp_output(pcb_); - if (err == ERR_ABRT) { - LWIP_LOG(" -> err ERR_ABRT"); - // sometimes lwip returns ERR_ABRT for no apparent reason - // the connection works fine afterwards, and back with ESPAsyncTCP we - // indirectly also ignored this error - // FIXME: figure out where this is returned and what it means in this context - return to_send; - } - if (err != ERR_OK) { - LWIP_LOG(" -> err %d", err); - errno = ECONNRESET; - return -1; - } - } return to_send; } + int internal_output() { + LWIP_LOG("tcp_output(%p)", pcb_); + err_t err = tcp_output(pcb_); + if (err == ERR_ABRT) { + LWIP_LOG(" -> err ERR_ABRT"); + // sometimes lwip returns ERR_ABRT for no apparent reason + // the connection works fine afterwards, and back with ESPAsyncTCP we + // indirectly also ignored this error + // FIXME: figure out where this is returned and what it means in this context + return 0; + } + if (err != ERR_OK) { + LWIP_LOG(" -> err %d", err); + errno = ECONNRESET; + return -1; + } + return 0; + } + ssize_t write(const void *buf, size_t len) override { + ssize_t written = internal_write(buf, len); + if (written == -1) + return -1; + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } + ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t written = 0; + for (int i = 0; i < iovcnt; i++) { + ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); + if (err == -1) { + if (written != 0) + // if we already read some don't return an error + break; + return err; + } + written += err; + if (err != iov[i].iov_len) + break; + } + if (written == 0) + // no need to output if nothing written + return 0; + int err = internal_output(); + if (err == -1) + return -1; + return written; + } int setblocking(bool blocking) override { if (pcb_ == nullptr) { errno = ECONNRESET; diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 7a5ce79161..9920610bf5 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,7 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; + virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; + virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; virtual int setblocking(bool blocking) = 0; virtual int loop() { return 0; }; }; From 272ceadbb00d5980aa00c0ad5043878a330d275f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:07:38 +0200 Subject: [PATCH 256/285] Redo docker build system with buildkit+multi-stage and cache pio packages (#2338) --- .devcontainer/devcontainer.json | 3 +- .github/workflows/ci-docker.yml | 5 + .github/workflows/ci.yml | 152 +++++++++--------- .github/workflows/docker-lint-build.yml | 100 ------------ .github/workflows/release.yml | 20 ++- docker/Dockerfile | 135 ++++++++++++++-- docker/Dockerfile.dev | 1 - docker/Dockerfile.hassio | 25 --- docker/Dockerfile.lint | 10 -- docker/build.py | 94 +++++------ docker/docker_entrypoint.sh | 18 +++ .../etc/cont-init.d/10-requirements.sh | 0 .../etc/cont-init.d/20-nginx.sh | 0 .../hassio-rootfs/etc/cont-init.d/30-dirs.sh | 9 ++ .../etc/nginx/includes/mime.types | 0 .../etc/nginx/includes/proxy_params.conf | 0 .../etc/nginx/includes/server_params.conf | 0 .../etc/nginx/includes/ssl_params.conf | 0 .../etc/nginx/nginx.conf | 0 .../etc/nginx/servers/direct-ssl.disabled | 0 .../etc/nginx/servers/direct.disabled | 0 .../etc/nginx/servers/ingress.conf | 0 .../etc/services.d/esphome/finish | 0 .../etc/services.d/esphome/run | 3 + .../etc/services.d/nginx/finish | 0 .../etc/services.d/nginx/run | 0 esphome/platformio_api.py | 4 +- script/ci-custom.py | 4 +- script/devcontainer-post-create | 5 +- 29 files changed, 295 insertions(+), 293 deletions(-) delete mode 100644 .github/workflows/docker-lint-build.yml delete mode 100644 docker/Dockerfile.dev delete mode 100644 docker/Dockerfile.hassio delete mode 100644 docker/Dockerfile.lint create mode 100755 docker/docker_entrypoint.sh rename docker/{rootfs => hassio-rootfs}/etc/cont-init.d/10-requirements.sh (100%) rename docker/{rootfs => hassio-rootfs}/etc/cont-init.d/20-nginx.sh (100%) create mode 100644 docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/mime.types (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/proxy_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/server_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/includes/ssl_params.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/nginx.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/direct-ssl.disabled (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/direct.disabled (100%) rename docker/{rootfs => hassio-rootfs}/etc/nginx/servers/ingress.conf (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/esphome/finish (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/esphome/run (89%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/nginx/finish (100%) rename docker/{rootfs => hassio-rootfs}/etc/services.d/nginx/run (100%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3904962d7c..433e5d2792 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,6 @@ { "name": "ESPHome Dev", - "context": "..", - "dockerFile": "../docker/Dockerfile.dev", + "image": "esphome/esphome-lint:dev", "postCreateCommand": [ "script/devcontainer-post-create" ], diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 45fd3e141b..33b15cb1dc 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -27,6 +27,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.9' + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set TAG run: | echo "TAG=check" >> $GITHUB_ENV diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b00e1b3835..875db0f9fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,61 +9,7 @@ on: pull_request: jobs: - ci-with-container: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - id: clang-format - name: Run script/clang-format - - id: clang-tidy - name: Run script/clang-tidy for ESP8266 - options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 1/4 - options: --environment esp32-tidy --split-num 4 --split-at 1 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 2/4 - options: --environment esp32-tidy --split-num 4 --split-at 2 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 3/4 - options: --environment esp32-tidy --split-num 4 --split-at 3 - - id: clang-tidy - name: Run script/clang-tidy for ESP32 4/4 - options: --environment esp32-tidy --split-num 4 --split-at 4 - - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: ghcr.io/esphome/esphome-lint:1.2 - steps: - - uses: actions/checkout@v2 - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - # Also run git-diff-index so that the step is marked as failed on formatting errors, - # since clang-format doesn't do anything but change files if -i is passed. - - name: Run clang-format - run: | - script/clang-format -i - git diff-index --quiet HEAD -- - if: ${{ matrix.id == 'clang-format' }} - - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix ${{ matrix.options }} - if: ${{ matrix.id == 'clang-tidy' }} - - - name: Suggested changes - run: script/ci-suggest-changes - if: always() - ci: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: @@ -77,48 +23,85 @@ jobs: - id: test file: tests/test1.yaml name: Test tests/test1.yaml + pio_cache_key: test1 - id: test file: tests/test2.yaml name: Test tests/test2.yaml + pio_cache_key: test2 - id: test file: tests/test3.yaml name: Test tests/test3.yaml + pio_cache_key: test1 - id: test file: tests/test4.yaml name: Test tests/test4.yaml + pio_cache_key: test4 - id: test file: tests/test5.yaml name: Test tests/test5.yaml + pio_cache_key: test5 - id: pytest name: Run pytest + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy for ESP8266 + options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 + pio_cache_key: tidyesp8266 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 1/4 + options: --environment esp32-tidy --split-num 4 --split-at 1 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 2/4 + options: --environment esp32-tidy --split-num 4 --split-at 2 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 3/4 + options: --environment esp32-tidy --split-num 4 --split-at 3 + pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 4/4 + options: --environment esp32-tidy --split-num 4 --split-at 4 + pio_cache_key: tidyesp32 steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 + id: python with: python-version: '3.7' - name: Cache pip modules - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} + key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} restore-keys: | - esphome-pip-3.7- - - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.file }}- - if: ${{ matrix.id == 'test' }} + pip-${{ steps.python.outputs.python-version }}- - name: Set up python environment - run: script/setup + run: | + pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt + pip3 install -e . + + # Use per check platformio cache because checks use different parts + - name: Cache platformio + uses: actions/cache@v2 + with: + path: ~/.platformio + key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} + restore-keys: | + platformio-${{ matrix.pio_cache_key }}- + if: matrix.id == 'test' || matrix.id == 'clang-tidy' + + - name: Install clang tools + run: | + sudo apt-get install \ + clang-format-11 \ + clang-tidy-11 + if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format' - name: Register problem matchers run: | @@ -127,20 +110,45 @@ jobs: echo "::add-matcher::.github/workflows/matchers/python.json" echo "::add-matcher::.github/workflows/matchers/pytest.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - name: Lint Custom run: | script/ci-custom.py script/build_codeowners.py --check - if: ${{ matrix.id == 'ci-custom' }} + if: matrix.id == 'ci-custom' + - name: Lint Python run: script/lint-python - if: ${{ matrix.id == 'lint-python' }} + if: matrix.id == 'lint-python' - run: esphome compile ${{ matrix.file }} - if: ${{ matrix.id == 'test' }} + if: matrix.id == 'test' + env: + # Also cache libdeps, store them in a ~/.platformio subfolder + PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps - name: Run pytest run: | pytest -vv --tb=native tests - if: ${{ matrix.id == 'pytest' }} + if: matrix.id == 'pytest' + + # Also run git-diff-index so that the step is marked as failed on formatting errors, + # since clang-format doesn't do anything but change files if -i is passed. + - name: Run clang-format + run: | + script/clang-format -i + git diff-index --quiet HEAD -- + if: matrix.id == 'clang-format' + + - name: Run clang-tidy + run: | + script/clang-tidy --all-headers --fix ${{ matrix.options }} + if: matrix.id == 'clang-tidy' + env: + # Also cache libdeps, store them in a ~/.platformio subfolder + PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps + + - name: Suggested changes + run: script/ci-suggest-changes + if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format') diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml deleted file mode 100644 index 8350d4c719..0000000000 --- a/.github/workflows/docker-lint-build.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Build and publish lint docker image - -# Only run when docker paths change -on: - push: - branches: [dev] - paths: - - 'docker/Dockerfile.lint' - - 'requirements.txt' - - 'requirements_optional.txt' - - 'requirements_test.txt' - - 'platformio.ini' - - '.github/workflows/docker-lint-build.yml' - -jobs: - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - strategy: - matrix: - arch: [amd64, armv7, aarch64] - build_type: ["lint"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Set TAG - run: | - echo "TAG=1.2" >> $GITHUB_ENV - - - name: Run build - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build - - - name: Log in to docker hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Run push - run: | - docker/build.py \ - --tag "${TAG}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - push - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - strategy: - matrix: - build_type: ["lint"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Set TAG - run: | - echo "TAG=1.2" >> $GITHUB_ENV - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - - name: Log in to docker hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to the GitHub container registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Run manifest - run: | - docker/build.py \ - --tag "${TAG}" \ - --build-type "${{ matrix.build_type }}" \ - manifest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3dc4952422..afd893d065 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["ha-addon", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - uses: actions/checkout@v2 - name: Set up Python @@ -65,13 +65,10 @@ jobs: with: python-version: '3.9' - - name: Run build - run: | - docker/build.py \ - --tag "${{ needs.init.outputs.tag }}" \ - --arch "${{ matrix.arch }}" \ - --build-type "${{ matrix.build_type }}" \ - build + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 - name: Log in to docker hub uses: docker/login-action@v1 @@ -85,13 +82,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Run push + - name: Build and push run: | docker/build.py \ --tag "${{ needs.init.outputs.tag }}" \ --arch "${{ matrix.arch }}" \ --build-type "${{ matrix.build_type }}" \ - push + build \ + --push deploy-docker-manifest: if: github.repository == 'esphome/esphome' @@ -99,7 +97,7 @@ jobs: needs: [init, deploy-docker] strategy: matrix: - build_type: ["ha-addon", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/docker/Dockerfile b/docker/Dockerfile index 907c041119..d3ee219de8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,55 @@ -ARG BUILD_FROM=esphome/esphome-base:latest -FROM ${BUILD_FROM} +# Build these with the build.py script +# Example: +# python3 docker/build.py --tag dev --arch amd64 --build-type docker build + +# One of "docker", "hassio" +ARG BASEIMGTYPE=docker + +FROM ghcr.io/hassio-addons/debian-base/amd64:5.0.0 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.0.0 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.0.0 AS base-hassio-armv7 +FROM debian:bullseye-20210816-slim AS base-docker-amd64 +FROM debian:bullseye-20210816-slim AS base-docker-arm64 +FROM debian:bullseye-20210816-slim AS base-docker-armv7 + +# Use TARGETARCH/TARGETVARIANT defined by docker +# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope +FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + python3=3.9.2-3 \ + python3-pip=20.3.4-4 \ + python3-setuptools=52.0.0-4 \ + python3-pil=8.1.2+dfsg-0.3 \ + python3-cryptography=3.3.2-1 \ + iputils-ping=3:20210202-1 \ + git=1:2.30.2-1 \ + curl=7.74.0-1.3+b1 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +ENV \ + # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/ + LANG=C.UTF-8 LC_ALL=C.UTF-8 \ + # Store globally installed pio libs in /piolibs + PLATFORMIO_GLOBALLIB_DIR=/piolibs + +RUN \ + # Ubuntu python3-pip is missing wheel + pip3 install --no-cache-dir \ + wheel==0.36.2 \ + platformio==5.2.0 \ + # Change some platformio settings + && platformio settings set enable_telemetry No \ + && platformio settings set check_libraries_interval 1000000 \ + && platformio settings set check_platformio_interval 1000000 \ + && platformio settings set check_platforms_interval 1000000 \ + && mkdir -p /piolibs # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / @@ -7,9 +57,14 @@ RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini -# Then copy esphome and install -COPY . . -RUN pip3 install --no-cache-dir -e . + + +# ======================= docker-type image ======================= +FROM base AS docker + +# Copy esphome and install +COPY . /esphome +RUN pip3 install --no-cache-dir -e /esphome # Settings for dashboard ENV USERNAME="" PASSWORD="" @@ -17,14 +72,74 @@ ENV USERNAME="" PASSWORD="" # Expose the dashboard to Docker EXPOSE 6052 -# Run healthcheck (heartbeat) -HEALTHCHECK --interval=30s --timeout=30s \ - CMD curl --fail http://localhost:6052 || exit 1 +COPY docker/docker_entrypoint.sh /entrypoint.sh # The directory the user should mount their configuration files to +VOLUME /config WORKDIR /config -# Set entrypoint to esphome so that the user doesn't have to type 'esphome' +# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome' # in every docker command twice -ENTRYPOINT ["esphome"] +ENTRYPOINT ["/entrypoint.sh"] # When no arguments given, start the dashboard in the workdir CMD ["dashboard", "/config"] + + + + +# ======================= hassio-type image ======================= +FROM base AS hassio + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + nginx=1.18.0-6.1 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +ARG BUILD_VERSION=dev + +# Copy root filesystem +COPY docker/hassio-rootfs/ / + +# Copy esphome and install +COPY . /esphome +RUN pip3 install --no-cache-dir -e /esphome + +# Labels +LABEL \ + io.hass.name="ESPHome" \ + io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + io.hass.type="addon" \ + io.hass.version="${BUILD_VERSION}" + # io.hass.arch is inherited from addon-debian-base + + + + +# ======================= lint-type image ======================= +FROM base AS lint + +ENV \ + PLATFORMIO_CORE_DIR=/esphome/.temp/platformio + +RUN \ + apt-get update \ + # Use pinned versions so that we get updates with build caching + && apt-get install -y --no-install-recommends \ + clang-format-11=1:11.0.1-2 \ + clang-tidy-11=1:11.0.1-2 \ + patch=2.7.6-7 \ + software-properties-common=0.96.20.2-2.1 \ + nano=5.4-2 \ + build-essential=12.9 \ + python3-dev=3.9.2-3 \ + && rm -rf \ + /tmp/* \ + /var/{cache,log}/* \ + /var/lib/apt/lists/* + +VOLUME ["/esphome"] +WORKDIR /esphome diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev deleted file mode 100644 index c646ce989b..0000000000 --- a/docker/Dockerfile.dev +++ /dev/null @@ -1 +0,0 @@ -FROM esphome/esphome-lint:1.2 diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio deleted file mode 100644 index ad80074ada..0000000000 --- a/docker/Dockerfile.hassio +++ /dev/null @@ -1,25 +0,0 @@ -ARG BUILD_FROM=esphome/esphome-hassio-base:latest -FROM ${BUILD_FROM} - -# First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini - -# Copy root filesystem -COPY docker/rootfs/ / - -# Then copy esphome and install -COPY . /opt/esphome/ -RUN pip3 install --no-cache-dir -e /opt/esphome - -# Build arguments -ARG BUILD_VERSION=dev - -# Labels -LABEL \ - io.hass.name="ESPHome" \ - io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ - io.hass.type="addon" \ - io.hass.version=${BUILD_VERSION} diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint deleted file mode 100644 index 3a090c3b41..0000000000 --- a/docker/Dockerfile.lint +++ /dev/null @@ -1,10 +0,0 @@ -ARG BUILD_FROM=esphome/esphome-lint-base:latest -FROM ${BUILD_FROM} - -COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ - && /platformio_install_deps.py /platformio.ini - -VOLUME ["/esphome"] -WORKDIR /esphome diff --git a/docker/build.py b/docker/build.py index c926b3653b..1904457989 100755 --- a/docker/build.py +++ b/docker/build.py @@ -2,7 +2,7 @@ from dataclasses import dataclass import subprocess import argparse -import platform +from platform import machine import shlex import re import sys @@ -24,9 +24,6 @@ TYPE_LINT = 'lint' TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] -BASE_VERSION = "4.2.0" - - parser = argparse.ArgumentParser() parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") @@ -34,27 +31,17 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) build_parser = subparsers.add_parser("build", help="Build the image") -push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +build_parser.add_argument("--push", help="Also push the images") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") - -# only lists some possibilities, doesn't have to be perfect -# https://stackoverflow.com/a/45125525 -UNAME_TO_ARCH = { - "x86_64": ARCH_AMD64, - "aarch64": ARCH_AARCH64, - "aarch64_be": ARCH_AARCH64, - "arm": ARCH_ARMV7, -} - - @dataclass(frozen=True) class DockerParams: - build_from: str build_to: str manifest_to: str - dockerfile: str + baseimgtype: str + platform: str + target: str @classmethod def for_type_arch(cls, build_type, arch): @@ -63,18 +50,28 @@ class DockerParams: TYPE_HA_ADDON: "esphome/esphome-hassio", TYPE_LINT: "esphome/esphome-lint" }[build_type] - build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" build_to = f"{prefix}-{arch}" - dockerfile = { - TYPE_DOCKER: "docker/Dockerfile", - TYPE_HA_ADDON: "docker/Dockerfile.hassio", - TYPE_LINT: "docker/Dockerfile.lint", + baseimgtype = { + TYPE_DOCKER: "docker", + TYPE_HA_ADDON: "hassio", + TYPE_LINT: "docker", + }[build_type] + platform = { + ARCH_AMD64: "linux/amd64", + ARCH_ARMV7: "linux/arm/v7", + ARCH_AARCH64: "linux/arm64", + }[arch] + target = { + TYPE_DOCKER: "docker", + TYPE_HA_ADDON: "hassio", + TYPE_LINT: "lint", }[build_type] return cls( - build_from=build_from, build_to=build_to, manifest_to=prefix, - dockerfile=dockerfile + baseimgtype=baseimgtype, + platform=platform, + target=target, ) @@ -117,41 +114,26 @@ def main(): CHANNEL_RELEASE: "latest", }[channel] cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" - run_command("docker", "pull", cache_img, ignore_error=True) - # 2. register QEMU binfmt (if not host arch) - is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch - if not is_native: - run_command( - "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", - "--reset", "-p", "yes" - ) - - # 3. build - run_command( - "docker", "build", - "--build-arg", f"BUILD_FROM={params.build_from}", - "--build-arg", f"BUILD_VERSION={args.tag}", - "--tag", f"{params.build_to}:{args.tag}", - "--cache-from", cache_img, - "--file", params.dockerfile, - "." - ) - elif args.command == "push": - params = DockerParams.for_type_arch(args.build_type, args.arch) imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] - src = imgs[0] - # 1. tag images - for img in imgs[1:]: - run_command( - "docker", "tag", src, img - ) - # 2. push images + + # 3. build + cmd = [ + "docker", "buildx", "build", + "--build-arg", f"BASEIMGTYPE={params.baseimgtype}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--cache-from", cache_img, + "--file", "docker/Dockerfile", + "--platform", params.platform, + "--target", params.target, + ] for img in imgs: - run_command( - "docker", "push", img - ) + cmd += ["--tag", img] + if args.push: + cmd.append("--push") + + run_command(*cmd, ".") elif args.command == "manifest": manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh new file mode 100755 index 0000000000..b3905d1fed --- /dev/null +++ b/docker/docker_entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# If /cache is mounted, use that as PIO's coredir +# otherwise use path in /config (so that PIO packages aren't downloaded on each compile) + +if [[ -d /cache ]]; then + export PLATFORMIO_CORE_DIR=/cache/platformio +else + export PLATFORMIO_CORE_DIR=/config/.esphome/platformio +fi + +if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then + echo "Creating cache directory ${PLATFORMIO_CORE_DIR}" + echo "You can change this behavior by mounting a directory to the container's /cache directory." + mkdir -p "${PLATFORMIO_CORE_DIR}" +fi + +exec esphome "$@" diff --git a/docker/rootfs/etc/cont-init.d/10-requirements.sh b/docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh similarity index 100% rename from docker/rootfs/etc/cont-init.d/10-requirements.sh rename to docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh diff --git a/docker/rootfs/etc/cont-init.d/20-nginx.sh b/docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh similarity index 100% rename from docker/rootfs/etc/cont-init.d/20-nginx.sh rename to docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh new file mode 100644 index 0000000000..301fe4db63 --- /dev/null +++ b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh @@ -0,0 +1,9 @@ +#!/usr/bin/with-contenv bashio +# ============================================================================== +# Community Hass.io Add-ons: ESPHome +# This files creates all directories used by esphome +# ============================================================================== + +PLATFORMIO_CORE_DIR=/data/cache/platformio + +mkdir -p "${PLATFORMIO_CORE_DIR}" diff --git a/docker/rootfs/etc/nginx/includes/mime.types b/docker/hassio-rootfs/etc/nginx/includes/mime.types similarity index 100% rename from docker/rootfs/etc/nginx/includes/mime.types rename to docker/hassio-rootfs/etc/nginx/includes/mime.types diff --git a/docker/rootfs/etc/nginx/includes/proxy_params.conf b/docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/proxy_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf diff --git a/docker/rootfs/etc/nginx/includes/server_params.conf b/docker/hassio-rootfs/etc/nginx/includes/server_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/server_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/server_params.conf diff --git a/docker/rootfs/etc/nginx/includes/ssl_params.conf b/docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf similarity index 100% rename from docker/rootfs/etc/nginx/includes/ssl_params.conf rename to docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/hassio-rootfs/etc/nginx/nginx.conf similarity index 100% rename from docker/rootfs/etc/nginx/nginx.conf rename to docker/hassio-rootfs/etc/nginx/nginx.conf diff --git a/docker/rootfs/etc/nginx/servers/direct-ssl.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled similarity index 100% rename from docker/rootfs/etc/nginx/servers/direct-ssl.disabled rename to docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled diff --git a/docker/rootfs/etc/nginx/servers/direct.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct.disabled similarity index 100% rename from docker/rootfs/etc/nginx/servers/direct.disabled rename to docker/hassio-rootfs/etc/nginx/servers/direct.disabled diff --git a/docker/rootfs/etc/nginx/servers/ingress.conf b/docker/hassio-rootfs/etc/nginx/servers/ingress.conf similarity index 100% rename from docker/rootfs/etc/nginx/servers/ingress.conf rename to docker/hassio-rootfs/etc/nginx/servers/ingress.conf diff --git a/docker/rootfs/etc/services.d/esphome/finish b/docker/hassio-rootfs/etc/services.d/esphome/finish similarity index 100% rename from docker/rootfs/etc/services.d/esphome/finish rename to docker/hassio-rootfs/etc/services.d/esphome/finish diff --git a/docker/rootfs/etc/services.d/esphome/run b/docker/hassio-rootfs/etc/services.d/esphome/run similarity index 89% rename from docker/rootfs/etc/services.d/esphome/run rename to docker/hassio-rootfs/etc/services.d/esphome/run index f806c50929..6218b200bd 100755 --- a/docker/rootfs/etc/services.d/esphome/run +++ b/docker/hassio-rootfs/etc/services.d/esphome/run @@ -22,5 +22,8 @@ if bashio::config.has_value 'relative_url'; then export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') fi +export PLATFORMIO_CORE_DIR=/data/cache/platformio +export PLATFORMIO_GLOBALLIB_DIR=/piolibs + bashio::log.info "Starting ESPHome dashboard..." exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio diff --git a/docker/rootfs/etc/services.d/nginx/finish b/docker/hassio-rootfs/etc/services.d/nginx/finish similarity index 100% rename from docker/rootfs/etc/services.d/nginx/finish rename to docker/hassio-rootfs/etc/services.d/nginx/finish diff --git a/docker/rootfs/etc/services.d/nginx/run b/docker/hassio-rootfs/etc/services.d/nginx/run similarity index 100% rename from docker/rootfs/etc/services.d/nginx/run rename to docker/hassio-rootfs/etc/services.d/nginx/run diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 6506a44e88..a99081a650 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -67,8 +67,8 @@ FILTER_PLATFORMIO_LINES = [ def run_platformio_cli(*args, **kwargs) -> Union[str, int]: os.environ["PLATFORMIO_FORCE_COLOR"] = "true" os.environ["PLATFORMIO_BUILD_DIR"] = os.path.abspath(CORE.relative_pioenvs_path()) - os.environ["PLATFORMIO_LIBDEPS_DIR"] = os.path.abspath( - CORE.relative_piolibdeps_path() + os.environ.setdefault( + "PLATFORMIO_LIBDEPS_DIR", os.path.abspath(CORE.relative_piolibdeps_path()) ) cmd = ["platformio"] + list(args) diff --git a/script/ci-custom.py b/script/ci-custom.py index cdc450a96b..3191842d8c 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -217,7 +217,9 @@ def lint_ext_check(fname): ) -@lint_file_check(exclude=["docker/rootfs/*", "docker/*.py", "script/*", "setup.py"]) +@lint_file_check( + exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"] +) def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] if ex != 100644: diff --git a/script/devcontainer-post-create b/script/devcontainer-post-create index 4b4dc5b6c9..120ab3307d 100755 --- a/script/devcontainer-post-create +++ b/script/devcontainer-post-create @@ -12,7 +12,6 @@ if [ ! -f $cpp_json ]; then pio init --ide vscode --silent sed -i "/\\/workspaces\/esphome\/include/d" $cpp_json else - echo "Cpp environment already configured. To reconfigure it you could run one the following commands:" - echo " pio init --ide vscode -e livingroom8266" - echo " pio init --ide vscode -e livingroom32" + echo "Cpp environment already configured. To reconfigure it you can run one the following commands:" + echo " pio init --ide vscode" fi From 5f21b925dad3a99819402bb294cef9182c956c45 Mon Sep 17 00:00:00 2001 From: synco Date: Mon, 20 Sep 2021 19:12:50 +1200 Subject: [PATCH 257/285] Calculating the AC only component of the samples (#1906) Co-authored-by: Synco Reynders Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/ct_clamp/ct_clamp_sensor.cpp | 46 ++++--------------- esphome/components/ct_clamp/ct_clamp_sensor.h | 15 +++--- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index c27134d6ac..130cdfefba 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -8,18 +8,6 @@ namespace ct_clamp { static const char *const TAG = "ct_clamp"; -void CTClampSensor::setup() { - this->is_calibrating_offset_ = true; - this->high_freq_.start(); - this->set_timeout("calibrate_offset", this->sample_duration_, [this]() { - this->high_freq_.stop(); - this->is_calibrating_offset_ = false; - if (this->num_samples_ != 0) { - this->offset_ = this->sample_sum_ / this->num_samples_; - } - }); -} - void CTClampSensor::dump_config() { LOG_SENSOR("", "CT Clamp Sensor", this); ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f); @@ -27,9 +15,6 @@ void CTClampSensor::dump_config() { } void CTClampSensor::update() { - if (this->is_calibrating_offset_) - return; - // Update only starts the sampling phase, in loop() the actual sampling is happening. // Request a high loop() execution interval during sampling phase. @@ -46,20 +31,23 @@ void CTClampSensor::update() { return; } - float raw = this->sample_sum_ / this->num_samples_; - float irms = std::sqrt(raw); - ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms); - this->publish_state(irms); + float dc = this->sample_sum_ / this->num_samples_; + float var = (this->sample_squared_sum_ / this->num_samples_) - dc * dc; + float ac = std::sqrt(var); + ESP_LOGD(TAG, "'%s' - Got %d samples", this->name_.c_str(), this->num_samples_); + ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA", this->name_.c_str(), ac); + this->publish_state(ac); }); // Set sampling values this->is_sampling_ = true; this->num_samples_ = 0; this->sample_sum_ = 0.0f; + this->sample_squared_sum_ = 0.0f; } void CTClampSensor::loop() { - if (!this->is_sampling_ && !this->is_calibrating_offset_) + if (!this->is_sampling_) return; // Perform a single sample @@ -67,22 +55,8 @@ void CTClampSensor::loop() { if (isnan(value)) return; - if (this->is_calibrating_offset_) { - this->sample_sum_ += value; - this->num_samples_++; - return; - } - - // Adjust DC offset via low pass filter (exponential moving average) - const float alpha = 0.001f; - this->offset_ = this->offset_ * (1 - alpha) + value * alpha; - - // Filtered value centered around the mid-point (0V) - float filtered = value - this->offset_; - - // IRMS is sqrt(∑v_i²) - float sq = filtered * filtered; - this->sample_sum_ += sq; + this->sample_sum_ += value; + this->sample_squared_sum_ += value * value; this->num_samples_++; } diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index c709f6718b..2f201c11a0 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -10,7 +10,6 @@ namespace ct_clamp { class CTClampSensor : public sensor::Sensor, public PollingComponent { public: - void setup() override; void update() override; void loop() override; void dump_config() override; @@ -35,17 +34,19 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent { * * Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino * - * This is automatically calculated with an exponential moving average/digital low pass filter. - * - * 0.5 is a good initial approximation to start with for most ESP8266 setups. + * The current clamp only measures AC, so any DC component is an unwanted artifact from the + * sampling circuit. The AC component is essentially the same as the calculating the Standard-Deviation, + * which can be done by cumulating 3 values per sample: + * 1) Number of samples + * 2) Sum of samples + * 3) Sum of sample squared + * https://en.wikipedia.org/wiki/Root_mean_square */ - float offset_ = 0.5f; float sample_sum_ = 0.0f; + float sample_squared_sum_ = 0.0f; uint32_t num_samples_ = 0; bool is_sampling_ = false; - /// Calibrate offset value once at boot - bool is_calibrating_offset_ = false; }; } // namespace ct_clamp From 82eca13d7b84a8972836b078531c1236bdd991bc Mon Sep 17 00:00:00 2001 From: besteru <69540218+besteru@users.noreply.github.com> Date: Mon, 20 Sep 2021 10:14:44 +0300 Subject: [PATCH 258/285] Fix error reporting for DHT bit read loop (#2344) --- esphome/components/dht/dht.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index a7f5747d68..734dde20a8 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -122,6 +122,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } + if (error_code != 0) + break; start_time = micros(); uint32_t end_time = start_time; @@ -136,6 +138,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, break; } } + if (error_code != 0) + break; if (i < 0) continue; From fff5ba03c25757ee45ad2b587a5948ebbd38f6a7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:29:09 +0200 Subject: [PATCH 259/285] Also run docker CI when requirements change (#2347) --- .github/workflows/ci-docker.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 33b15cb1dc..12f5a7dfc2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -7,11 +7,15 @@ on: paths: - 'docker/**' - '.github/workflows/**' + - 'requirements*.txt' + - 'platformio.ini' pull_request: paths: - 'docker/**' - '.github/workflows/**' + - 'requirements*.txt' + - 'platformio.ini' jobs: check-docker: From 945ed5d3bda2a1080f875eb6ad796e72c38adbb2 Mon Sep 17 00:00:00 2001 From: synco Date: Mon, 20 Sep 2021 19:29:47 +1200 Subject: [PATCH 260/285] Added graphing component (#2109) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen Co-authored-by: Synco Reynders Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/display/display_buffer.cpp | 7 + esphome/components/display/display_buffer.h | 28 ++ esphome/components/graph/__init__.py | 216 +++++++++++ esphome/components/graph/graph.cpp | 361 ++++++++++++++++++ esphome/components/graph/graph.h | 178 +++++++++ esphome/const.py | 14 + esphome/core/defines.h | 1 + 8 files changed, 806 insertions(+) create mode 100644 esphome/components/graph/__init__.py create mode 100644 esphome/components/graph/graph.cpp create mode 100644 esphome/components/graph/graph.h diff --git a/CODEOWNERS b/CODEOWNERS index 8aa96d14af..ab3a0815ce 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,6 +54,7 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle +esphome/components/graph/* @synco esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 9c4ee3189f..bbb7444cb5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -233,6 +233,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } +#ifdef USE_GRAPH +void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); } +void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) { + graph->draw_legend(this, x, y, color_on); +} +#endif // USE_GRAPH + void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b89eab0dba..3f89d3f8d2 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -9,6 +9,10 @@ #include "esphome/components/time/real_time_clock.h" #endif +#ifdef USE_GRAPH +#include "esphome/components/graph/graph.h" +#endif + namespace esphome { namespace display { @@ -273,6 +277,30 @@ class DisplayBuffer { */ void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); +#ifdef USE_GRAPH + /** Draw the `graph` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); + + /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param name_font The font used for the trace name + * @param value_font The font used for the trace value and units + * @param color_on The color of the border + */ + void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); +#endif // USE_GRAPH + /** Get the text bounds of the given string. * * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py new file mode 100644 index 0000000000..12acfee869 --- /dev/null +++ b/esphome/components/graph/__init__.py @@ -0,0 +1,216 @@ +from esphome.components.font import Font +from esphome.components import sensor, color +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_COLOR, + CONF_DIRECTION, + CONF_DURATION, + CONF_ID, + CONF_LEGEND, + CONF_NAME, + CONF_NAME_FONT, + CONF_SHOW_LINES, + CONF_SHOW_UNITS, + CONF_SHOW_VALUES, + CONF_VALUE_FONT, + CONF_WIDTH, + CONF_SENSOR, + CONF_HEIGHT, + CONF_MIN_VALUE, + CONF_MAX_VALUE, + CONF_MIN_RANGE, + CONF_MAX_RANGE, + CONF_LINE_THICKNESS, + CONF_LINE_TYPE, + CONF_X_GRID, + CONF_Y_GRID, + CONF_BORDER, + CONF_TRACES, +) + +CODEOWNERS = ["@synco"] + +DEPENDENCIES = ["display", "sensor"] +MULTI_CONF = True + +graph_ns = cg.esphome_ns.namespace("graph") +Graph_ = graph_ns.class_("Graph", cg.Component) +GraphTrace = graph_ns.class_("GraphTrace") +GraphLegend = graph_ns.class_("GraphLegend") + +LineType = graph_ns.enum("LineType") +LINE_TYPE = { + "SOLID": LineType.LINE_TYPE_SOLID, + "DOTTED": LineType.LINE_TYPE_DOTTED, + "DASHED": LineType.LINE_TYPE_DASHED, +} + +DirectionType = graph_ns.enum("DirectionType") +DIRECTION_TYPE = { + "AUTO": DirectionType.DIRECTION_TYPE_AUTO, + "HORIZONTAL": DirectionType.DIRECTION_TYPE_HORIZONTAL, + "VERTICAL": DirectionType.DIRECTION_TYPE_VERTICAL, +} + +ValuePositionType = graph_ns.enum("ValuePositionType") +VALUE_POSITION_TYPE = { + "NONE": ValuePositionType.VALUE_POSITION_TYPE_NONE, + "AUTO": ValuePositionType.VALUE_POSITION_TYPE_AUTO, + "BESIDE": ValuePositionType.VALUE_POSITION_TYPE_BESIDE, + "BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW, +} + + +GRAPH_TRACE_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(GraphTrace), + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_NAME): cv.string, + cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, + cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), + cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + } +) + +GRAPH_LEGEND_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(GraphLegend), + cv.Required(CONF_NAME_FONT): cv.use_id(Font), + cv.Optional(CONF_VALUE_FONT): cv.use_id(Font), + cv.Optional(CONF_WIDTH): cv.positive_not_null_int, + cv.Optional(CONF_HEIGHT): cv.positive_not_null_int, + cv.Optional(CONF_BORDER): cv.boolean, + cv.Optional(CONF_SHOW_LINES): cv.boolean, + cv.Optional(CONF_SHOW_VALUES): cv.enum(VALUE_POSITION_TYPE, upper=True), + cv.Optional(CONF_SHOW_UNITS): cv.boolean, + cv.Optional(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True), + } +) + + +GRAPH_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(Graph_), + cv.Required(CONF_DURATION): cv.positive_time_period_seconds, + cv.Required(CONF_WIDTH): cv.positive_not_null_int, + cv.Required(CONF_HEIGHT): cv.positive_not_null_int, + cv.Optional(CONF_X_GRID): cv.positive_time_period_seconds, + cv.Optional(CONF_Y_GRID): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_BORDER): cv.boolean, + # Single trace options in base + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, + cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), + cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + # Axis specific options (Future feature may be to add second Y-axis) + cv.Optional(CONF_MIN_VALUE): cv.float_, + cv.Optional(CONF_MAX_VALUE): cv.float_, + cv.Optional(CONF_MIN_RANGE): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_MAX_RANGE): cv.float_range(min=0, min_included=False), + cv.Optional(CONF_TRACES): cv.ensure_list(GRAPH_TRACE_SCHEMA), + cv.Optional(CONF_LEGEND): cv.ensure_list(GRAPH_LEGEND_SCHEMA), + } +) + + +def _relocate_fields_to_subfolder(config, subfolder, subschema): + fields = [k.schema for k in subschema.schema.keys()] + fields.remove(CONF_ID) + if subfolder in config: + # Ensure no ambigious fields in base of config + for f in fields: + if f in config: + raise cv.Invalid( + "You cannot use the '" + + str(f) + + "' field when already using 'traces:'. " + "Please move it into 'traces:' entry." + ) + else: + # Copy over all fields to subfolder: + trace = {} + for f in fields: + if f in config: + trace[f] = config.pop(f) + config[subfolder] = cv.ensure_list(subschema)(trace) + return config + + +def _relocate_trace(config): + return _relocate_fields_to_subfolder(config, CONF_TRACES, GRAPH_TRACE_SCHEMA) + + +CONFIG_SCHEMA = cv.All( + GRAPH_SCHEMA, + _relocate_trace, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_duration(config[CONF_DURATION])) + cg.add(var.set_width(config[CONF_WIDTH])) + cg.add(var.set_height(config[CONF_HEIGHT])) + await cg.register_component(var, config) + + # Graph options + if CONF_X_GRID in config: + cg.add(var.set_grid_x(config[CONF_X_GRID])) + if CONF_Y_GRID in config: + cg.add(var.set_grid_y(config[CONF_Y_GRID])) + if CONF_BORDER in config: + cg.add(var.set_border(config[CONF_BORDER])) + # Axis related options + if CONF_MIN_VALUE in config: + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + if CONF_MAX_VALUE in config: + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + if CONF_MIN_RANGE in config: + cg.add(var.set_min_range(config[CONF_MIN_RANGE])) + if CONF_MAX_RANGE in config: + cg.add(var.set_max_range(config[CONF_MAX_RANGE])) + # Trace options + for trace in config[CONF_TRACES]: + tr = cg.new_Pvariable(trace[CONF_ID], GraphTrace()) + sens = await cg.get_variable(trace[CONF_SENSOR]) + cg.add(tr.set_sensor(sens)) + if CONF_NAME in trace: + cg.add(tr.set_name(trace[CONF_NAME])) + else: + cg.add(tr.set_name(trace[CONF_SENSOR].id)) + if CONF_LINE_THICKNESS in trace: + cg.add(tr.set_line_thickness(trace[CONF_LINE_THICKNESS])) + if CONF_LINE_TYPE in trace: + cg.add(tr.set_line_type(trace[CONF_LINE_TYPE])) + if CONF_COLOR in trace: + c = await cg.get_variable(trace[CONF_COLOR]) + cg.add(tr.set_line_color(c)) + cg.add(var.add_trace(tr)) + # Add legend + if CONF_LEGEND in config: + lgd = config[CONF_LEGEND][0] + legend = cg.new_Pvariable(lgd[CONF_ID], GraphLegend()) + if CONF_NAME_FONT in lgd: + font = await cg.get_variable(lgd[CONF_NAME_FONT]) + cg.add(legend.set_name_font(font)) + if CONF_VALUE_FONT in lgd: + font = await cg.get_variable(lgd[CONF_VALUE_FONT]) + cg.add(legend.set_value_font(font)) + if CONF_WIDTH in lgd: + cg.add(legend.set_width(lgd[CONF_WIDTH])) + if CONF_HEIGHT in lgd: + cg.add(legend.set_height(lgd[CONF_HEIGHT])) + if CONF_BORDER in lgd: + cg.add(legend.set_border(lgd[CONF_BORDER])) + if CONF_SHOW_LINES in lgd: + cg.add(legend.set_lines(lgd[CONF_SHOW_LINES])) + if CONF_SHOW_VALUES in lgd: + cg.add(legend.set_values(lgd[CONF_SHOW_VALUES])) + if CONF_SHOW_UNITS in lgd: + cg.add(legend.set_units(lgd[CONF_SHOW_UNITS])) + if CONF_DIRECTION in lgd: + cg.add(legend.set_direction(lgd[CONF_DIRECTION])) + cg.add(var.add_legend(legend)) + + cg.add_define("USE_GRAPH") diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp new file mode 100644 index 0000000000..6ede553fdb --- /dev/null +++ b/esphome/components/graph/graph.cpp @@ -0,0 +1,361 @@ +#include "graph.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/color.h" +#include "esphome/core/log.h" +#include "esphome/core/esphal.h" +#include +#include +#include // std::cout, std::fixed +#include +namespace esphome { +namespace graph { + +using namespace display; + +static const char *const TAG = "graph"; +static const char *const TAGL = "graphlegend"; + +void HistoryData::init(int length) { + this->length_ = length; + this->samples_.resize(length, NAN); + this->last_sample_ = millis(); +} + +void HistoryData::take_sample(float data) { + uint32_t tm = millis(); + uint32_t dt = tm - last_sample_; + last_sample_ = tm; + + // Step data based on time + this->period_ += dt; + while (this->period_ >= this->update_time_) { + this->samples_[this->count_] = data; + this->period_ -= this->update_time_; + this->count_ = (this->count_ + 1) % this->length_; + ESP_LOGV(TAG, "Updating trace with value: %f", data); + } + if (!isnan(data)) { + // Recalc recent max/min + this->recent_min_ = data; + this->recent_max_ = data; + for (int i = 0; i < this->length_; i++) { + if (!isnan(this->samples_[i])) { + if (this->recent_max_ < this->samples_[i]) + this->recent_max_ = this->samples_[i]; + if (this->recent_min_ > this->samples_[i]) + this->recent_min_ = this->samples_[i]; + } + } + } +} + +void GraphTrace::init(Graph *g) { + ESP_LOGI(TAG, "Init trace for sensor %s", this->get_name().c_str()); + this->data_.init(g->get_width()); + sensor_->add_on_state_callback([this](float state) { this->data_.take_sample(state); }); + this->data_.set_update_time_ms(g->get_duration() * 1000 / g->get_width()); +} + +void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { + /// Plot border + if (this->border_) { + buff->horizontal_line(x_offset, y_offset, this->width_, color); + buff->horizontal_line(x_offset, y_offset + this->height_ - 1, this->width_, color); + buff->vertical_line(x_offset, y_offset, this->height_, color); + buff->vertical_line(x_offset + this->width_ - 1, y_offset, this->height_, color); + } + /// Determine best y-axis scale and range + float ymin = NAN; + float ymax = NAN; + for (auto *trace : traces_) { + float mx = trace->get_tracedata()->get_recent_max(); + float mn = trace->get_tracedata()->get_recent_min(); + if (isnan(ymax) || (ymax < mx)) + ymax = mx; + if (isnan(ymin) || (ymin > mn)) + ymin = mn; + } + // Adjust if manually overridden + if (!isnan(this->min_value_)) + ymin = this->min_value_; + if (!isnan(this->max_value_)) + ymax = this->max_value_; + + float yrange = ymax - ymin; + if (yrange > this->max_range_) { + // Look back in trace data to best-fit into local range + float mx = NAN; + float mn = NAN; + for (int16_t i = 0; i < this->width_; i++) { + for (auto *trace : traces_) { + float v = trace->get_tracedata()->get_value(i); + if (!isnan(v)) { + if ((v - mn) > this->max_range_) + break; + if ((mx - v) > this->max_range_) + break; + if (isnan(mx) || (v > mx)) + mx = v; + if (isnan(mn) || (v < mn)) + mn = v; + } + } + } + yrange = this->max_range_; + if (!isnan(mn)) { + ymin = mn; + ymax = ymin + this->max_range_; + } + ESP_LOGV(TAG, "Graphing at max_range. Using local min %f, max %f", mn, mx); + } + + float y_per_div = this->min_range_; + if (!isnan(this->gridspacing_y_)) { + y_per_div = this->gridspacing_y_; + } + // Restrict drawing too many gridlines + if (yrange > 10 * y_per_div) { + while (yrange > 10 * y_per_div) { + y_per_div *= 2; + } + ESP_LOGW(TAG, "Graphing reducing y-scale to prevent too many gridlines"); + } + + // Adjust limits to nice y_per_div boundaries + int yn = int(ymin / y_per_div); + int ym = int(ymax / y_per_div) + int(1 * (fmodf(ymax, y_per_div) != 0)); + ymin = yn * y_per_div; + ymax = ym * y_per_div; + yrange = ymax - ymin; + + /// Draw grid + if (!isnan(this->gridspacing_y_)) { + for (int y = yn; y <= ym; y++) { + int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); + for (int x = 0; x < this->width_; x += 2) { + buff->draw_pixel_at(x_offset + x, y_offset + py, color); + } + } + } + if (!isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { + int n = this->duration_ / this->gridspacing_x_; + // Restrict drawing too many gridlines + if (n > 20) { + while (n > 20) { + n /= 2; + } + ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); + } + for (int i = 0; i <= n; i++) { + for (int y = 0; y < this->height_; y += 2) { + buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); + } + } + } + + /// Draw traces + ESP_LOGV(TAG, "Updating graph. ymin %f, ymax %f", ymin, ymax); + for (auto *trace : traces_) { + Color c = trace->get_line_color(); + uint16_t thick = trace->get_line_thickness(); + for (int16_t i = 0; i < this->width_; i++) { + float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; + if (!isnan(v) && (thick > 0)) { + int16_t x = this->width_ - 1 - i; + uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; + if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { + int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; + for (int16_t t = 0; t < thick; t++) { + buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); + } + } + } + } + } +} + +/// Determine the best coordinates of drawing text + lines +void GraphLegend::init(Graph *g) { + parent_ = g; + + // Determine maximum expected text and value width / height + int txtw = 0, txtos = 0, txtbl = 0, txth = 0; + int valw = 0, valos = 0, valbl = 0, valh = 0; + int lt = 0; + for (auto *trace : g->traces_) { + std::string txtstr = trace->get_name(); + int fw, fos, fbl, fh; + this->font_label->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh); + if (fw > txtw) + txtw = fw; + if (fh > txth) + txth = fh; + if (trace->get_line_thickness() > lt) + lt = trace->get_line_thickness(); + ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh); + + if (this->values_ != VALUE_POSITION_TYPE_NONE) { + std::stringstream ss; + ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state(); + std::string valstr = ss.str(); + if (this->units_) { + valstr += trace->sensor_->get_unit_of_measurement(); + } + this->font_value->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); + if (fw > valw) + valw = fw; + if (fh > valh) + valh = fh; + ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh); + } + } + // Add extra margin + txtw *= 1.2; + valw *= 1.2; + + uint8_t n = g->traces_.size(); + uint16_t w = this->width_; + uint16_t h = this->height_; + DirectionType dir = this->direction_; + ValuePositionType valpos = this->values_; + if (!this->font_value) { + valpos = VALUE_POSITION_TYPE_NONE; + } + // Line sample always goes below text for compactness + this->yl = txth + (txth / 4) + lt / 2; + + if (dir == DIRECTION_TYPE_AUTO) { + dir = DIRECTION_TYPE_HORIZONTAL; // as default + if (h > 0) { + dir = DIRECTION_TYPE_VERTICAL; + } + } + + if (valpos == VALUE_POSITION_TYPE_AUTO) { + // TODO: do something smarter?? - fit to w and h? + valpos = VALUE_POSITION_TYPE_BELOW; + } + + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->yv = txth + (txth / 4); + if (this->lines_) + this->yv += txth / 4 + lt; + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + this->xv = (txtw + valw) / 2; + } + + // If width or height is specified we divide evenly within, else we do tight-fit + if (w == 0) { + this->x0 = txtw / 2; + this->xs = txtw; + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->xs = std::max(txtw, valw); + ; + this->x0 = this->xs / 2; + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + this->xs = txtw + valw; + } + if (dir == DIRECTION_TYPE_VERTICAL) { + this->width_ = this->xs; + } else { + this->width_ = this->xs * n; + } + } else { + this->xs = w / n; + this->x0 = this->xs / 2; + } + + if (h == 0) { + this->ys = txth; + if (valpos == VALUE_POSITION_TYPE_BELOW) { + this->ys = txth + txth / 2 + valh; + if (this->lines_) { + this->ys += lt; + } + } else if (valpos == VALUE_POSITION_TYPE_BESIDE) { + if (this->lines_) { + this->ys = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4); + } else { + this->ys = std::max(txth + txth / 4, valh + valh / 4); + } + this->height_ = this->ys * n; + } + if (dir == DIRECTION_TYPE_HORIZONTAL) { + this->height_ = this->ys; + } else { + this->height_ = this->ys * n; + } + } else { + this->ys = h / n; + } + + if (dir == DIRECTION_TYPE_HORIZONTAL) { + this->ys = 0; + } else { + this->xs = 0; + } +} + +void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { + if (!legend_) + return; + + /// Plot border + if (this->border_) { + int w = legend_->width_; + int h = legend_->height_; + buff->horizontal_line(x_offset, y_offset, w, color); + buff->horizontal_line(x_offset, y_offset + h - 1, w, color); + buff->vertical_line(x_offset, y_offset, h, color); + buff->vertical_line(x_offset + w - 1, y_offset, h, color); + } + + int x = x_offset + legend_->x0; + int y = y_offset; + for (auto *trace : traces_) { + std::string txtstr = trace->get_name(); + ESP_LOGV(TAG, " %s", txtstr.c_str()); + + buff->printf(x, y, legend_->font_label, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", txtstr.c_str()); + + if (legend_->lines_) { + uint16_t thick = trace->get_line_thickness(); + for (int16_t i = 0; i < legend_->x0 * 4 / 3; i++) { + uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; + if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { + buff->vertical_line(x - legend_->x0 * 2 / 3 + i, y + legend_->yl - thick / 2, thick, trace->get_line_color()); + } + } + } + + if (legend_->values_ != VALUE_POSITION_TYPE_NONE) { + int xv = x + legend_->xv; + int yv = y + legend_->yv; + std::stringstream ss; + ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state(); + std::string valstr = ss.str(); + if (legend_->units_) { + valstr += trace->sensor_->get_unit_of_measurement(); + } + buff->printf(xv, yv, legend_->font_value, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); + ESP_LOGV(TAG, " value: %s", valstr.c_str()); + } + x += legend_->xs; + y += legend_->ys; + } +} + +void Graph::setup() { + for (auto *trace : traces_) { + trace->init(this); + } +} + +void Graph::dump_config() { + for (auto *trace : traces_) { + ESP_LOGCONFIG(TAG, "Graph for sensor %s", trace->get_name().c_str()); + } +} + +} // namespace graph +} // namespace esphome diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h new file mode 100644 index 0000000000..8f6e74f67a --- /dev/null +++ b/esphome/components/graph/graph.h @@ -0,0 +1,178 @@ +#pragma once +#include +#include "esphome/core/color.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { + +// forward declare DisplayBuffer +namespace display { +class DisplayBuffer; +class Font; +} // namespace display + +namespace graph { + +class Graph; + +const Color COLOR_ON(255, 255, 255, 255); + +/// Bit pattern defines the line-type +enum LineType { + LINE_TYPE_SOLID = 0b1111, + LINE_TYPE_DOTTED = 0b0101, + LINE_TYPE_DASHED = 0b1110, + // Following defines number of bits used to define line pattern + PATTERN_LENGTH = 4 +}; + +enum DirectionType { + DIRECTION_TYPE_AUTO, + DIRECTION_TYPE_HORIZONTAL, + DIRECTION_TYPE_VERTICAL, +}; + +enum ValuePositionType { + VALUE_POSITION_TYPE_NONE, + VALUE_POSITION_TYPE_AUTO, + VALUE_POSITION_TYPE_BESIDE, + VALUE_POSITION_TYPE_BELOW +}; + +class GraphLegend { + public: + void init(Graph *g); + void set_name_font(display::Font *font) { this->font_label = font; } + void set_value_font(display::Font *font) { this->font_value = font; } + void set_width(uint32_t width) { this->width_ = width; } + void set_height(uint32_t height) { this->height_ = height; } + void set_border(bool val) { this->border_ = val; } + void set_lines(bool val) { this->lines_ = val; } + void set_values(ValuePositionType val) { this->values_ = val; } + void set_units(bool val) { this->units_ = val; } + void set_direction(DirectionType val) { this->direction_ = val; } + + protected: + uint32_t width_{0}; + uint32_t height_{0}; + bool border_{true}; + bool lines_{true}; + ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; + bool units_{true}; + DirectionType direction_{DIRECTION_TYPE_AUTO}; + display::Font *font_label{nullptr}; + display::Font *font_value{nullptr}; + // Calculated values + Graph *parent_{nullptr}; + // (x0) (xs,ys) (xs,ys) + // ------> LABEL1 -------> LABEL2 -------> ... + // | \(xv,yv) \ . + // | \ \-> VALUE1+units + // (0,yl)| \-> VALUE1+units + // v (top_center) + // LINE_SAMPLE + int x0{0}; // X-offset to centre of label text + int xs{0}; // X spacing between labels + int ys{0}; // Y spacing between labels + int yl{0}; // Y spacing from label to line sample + int xv{0}; // X distance between label to value text + int yv{0}; // Y distance between label to value text + friend Graph; +}; + +class HistoryData { + public: + void init(int length); + ~HistoryData(); + void set_update_time_ms(uint32_t update_time_ms) { update_time_ = update_time_ms; } + void take_sample(float data); + int get_length() const { return length_; } + float get_value(int idx) const { return samples_[(count_ + length_ - 1 - idx) % length_]; } + float get_recent_max() const { return recent_max_; } + float get_recent_min() const { return recent_min_; } + + protected: + uint32_t last_sample_; + uint32_t period_{0}; /// in ms + uint32_t update_time_{0}; /// in ms + int length_; + int count_{0}; + float recent_min_{NAN}; + float recent_max_{NAN}; + std::vector samples_; +}; + +class GraphTrace { + public: + void init(Graph *g); + void set_name(std::string name) { name_ = name; } + void set_sensor(sensor::Sensor *sensor) { sensor_ = sensor; } + uint8_t get_line_thickness() { return this->line_thickness_; } + void set_line_thickness(uint8_t val) { this->line_thickness_ = val; } + enum LineType get_line_type() { return this->line_type_; } + void set_line_type(enum LineType val) { this->line_type_ = val; } + Color get_line_color() { return this->line_color_; } + void set_line_color(Color val) { this->line_color_ = val; } + const std::string get_name(void) { return name_; } + const HistoryData *get_tracedata() { return &data_; } + + protected: + sensor::Sensor *sensor_{nullptr}; + std::string name_{""}; + uint8_t line_thickness_{3}; + enum LineType line_type_ { LINE_TYPE_SOLID }; + Color line_color_{COLOR_ON}; + HistoryData data_; + + friend Graph; + friend GraphLegend; +}; + +class Graph : public Component { + public: + void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); + void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); + + void setup() override; + float get_setup_priority() const override { return setup_priority::PROCESSOR; } + void dump_config() override; + + void set_duration(uint32_t duration) { duration_ = duration; } + void set_width(uint32_t width) { width_ = width; } + void set_height(uint32_t height) { height_ = height; } + void set_min_value(float val) { this->min_value_ = val; } + void set_max_value(float val) { this->max_value_ = val; } + void set_min_range(float val) { this->min_range_ = val; } + void set_max_range(float val) { this->max_range_ = val; } + void set_grid_x(float val) { this->gridspacing_x_ = val; } + void set_grid_y(float val) { this->gridspacing_y_ = val; } + void set_border(bool val) { this->border_ = val; } + void add_trace(GraphTrace *trace) { traces_.push_back(trace); } + void add_legend(GraphLegend *legend) { + this->legend_ = legend; + legend->init(this); + } + uint32_t get_duration() { return duration_; } + uint32_t get_width() { return width_; } + uint32_t get_height() { return height_; } + + protected: + uint32_t duration_; /// in seconds + uint32_t width_; /// in pixels + uint32_t height_; /// in pixels + float min_value_{NAN}; + float max_value_{NAN}; + float min_range_{1.0}; + float max_range_{NAN}; + float gridspacing_x_{NAN}; + float gridspacing_y_{NAN}; + bool border_{true}; + std::vector traces_; + GraphLegend *legend_{nullptr}; + + friend GraphLegend; +}; + +} // namespace graph +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index a74068ab77..598b6351b4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -90,6 +90,7 @@ CONF_BIT_DEPTH = "bit_depth" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" +CONF_BORDER = "border" CONF_BRANCH = "branch" CONF_BRIGHTNESS = "brightness" CONF_BROKER = "broker" @@ -327,6 +328,7 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LEGEND = "legend" CONF_LENGTH = "length" CONF_LEVEL = "level" CONF_LG = "lg" @@ -334,6 +336,8 @@ CONF_LIBRARIES = "libraries" CONF_LIGHT = "light" CONF_LIGHTNING_ENERGY = "lightning_energy" CONF_LIGHTNING_THRESHOLD = "lightning_threshold" +CONF_LINE_THICKNESS = "line_thickness" +CONF_LINE_TYPE = "line_type" CONF_LOADED_INTEGRATIONS = "loaded_integrations" CONF_LOCAL = "local" CONF_LOG_TOPIC = "log_topic" @@ -355,6 +359,7 @@ CONF_MAX_HEATING_RUN_TIME = "max_heating_run_time" CONF_MAX_LENGTH = "max_length" CONF_MAX_LEVEL = "max_level" CONF_MAX_POWER = "max_power" +CONF_MAX_RANGE = "max_range" CONF_MAX_REFRESH_RATE = "max_refresh_rate" CONF_MAX_SPEED = "max_speed" CONF_MAX_TEMPERATURE = "max_temperature" @@ -376,6 +381,7 @@ CONF_MIN_IDLE_TIME = "min_idle_time" CONF_MIN_LENGTH = "min_length" CONF_MIN_LEVEL = "min_level" CONF_MIN_POWER = "min_power" +CONF_MIN_RANGE = "min_range" CONF_MIN_TEMPERATURE = "min_temperature" CONF_MIN_VALUE = "min_value" CONF_MINUTE = "minute" @@ -393,6 +399,7 @@ CONF_MQTT_ID = "mqtt_id" CONF_MULTIPLEXER = "multiplexer" CONF_MULTIPLY = "multiply" CONF_NAME = "name" +CONF_NAME_FONT = "name_font" CONF_NBITS = "nbits" CONF_NEC = "nec" CONF_NETWORKS = "networks" @@ -584,6 +591,9 @@ CONF_SERVICES = "services" CONF_SET_POINT_MINIMUM_DIFFERENTIAL = "set_point_minimum_differential" CONF_SETUP_MODE = "setup_mode" CONF_SETUP_PRIORITY = "setup_priority" +CONF_SHOW_LINES = "show_lines" +CONF_SHOW_UNITS = "show_units" +CONF_SHOW_VALUES = "show_values" CONF_SHUNT_RESISTANCE = "shunt_resistance" CONF_SHUNT_VOLTAGE = "shunt_voltage" CONF_SHUTDOWN_MESSAGE = "shutdown_message" @@ -659,6 +669,7 @@ CONF_TOLERANCE = "tolerance" CONF_TOPIC = "topic" CONF_TOPIC_PREFIX = "topic_prefix" CONF_TOTAL = "total" +CONF_TRACES = "traces" CONF_TRANSITION_LENGTH = "transition_length" CONF_TRIGGER_ID = "trigger_id" CONF_TRIGGER_PIN = "trigger_pin" @@ -681,6 +692,7 @@ CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" CONF_VALUE = "value" +CONF_VALUE_FONT = "value_font" CONF_VARIABLES = "variables" CONF_VARIANT = "variant" CONF_VERSION = "version" @@ -704,6 +716,8 @@ CONF_WILL_MESSAGE = "will_message" CONF_WIND_DIRECTION_DEGREES = "wind_direction_degrees" CONF_WIND_SPEED = "wind_speed" CONF_WINDOW_SIZE = "window_size" +CONF_X_GRID = "x_grid" +CONF_Y_GRID = "y_grid" CONF_ZERO = "zero" ENV_NOGITIGNORE = "ESPHOME_NOGITIGNORE" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e3976d378e..89010ce246 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,6 +19,7 @@ #define USE_DEEP_SLEEP #define USE_ESP8266_PREFERENCES_FLASH #define USE_FAN +#define USE_GRAPH #define USE_HOMEASSISTANT_TIME #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_I2C_MULTIPLEXER From 81685573e1fc760259d6d0eae467807acdfdd479 Mon Sep 17 00:00:00 2001 From: poptix Date: Mon, 20 Sep 2021 02:44:18 -0500 Subject: [PATCH 261/285] Properly calculate negative temperatures in sm300d2 (#2335) Co-authored-by: Matt Hallacy --- esphome/components/sm300d2/sm300d2.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index d542582fcb..e41a4855db 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -29,8 +29,11 @@ void SM300D2Sensor::update() { } uint16_t calculated_checksum = this->sm300d2_checksum_(response); + // Occasionally the checksum has a +/- 0x80 offset. Negative temperatures are + // responsible for some of these. The rest are unknown/undocumented. if ((calculated_checksum != response[SM300D2_RESPONSE_LENGTH - 1]) && - (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { + (calculated_checksum - 0x80 != response[SM300D2_RESPONSE_LENGTH - 1]) && + (calculated_checksum + 0x80 != response[SM300D2_RESPONSE_LENGTH - 1])) { ESP_LOGW(TAG, "SM300D2 Checksum doesn't match: 0x%02X!=0x%02X", response[SM300D2_RESPONSE_LENGTH - 1], calculated_checksum); this->status_set_warning(); @@ -46,7 +49,10 @@ void SM300D2Sensor::update() { const uint16_t tvoc = (response[6] * 256) + response[7]; const uint16_t pm_2_5 = (response[8] * 256) + response[9]; const uint16_t pm_10_0 = (response[10] * 256) + response[11]; - const float temperature = response[12] + (response[13] * 0.1); + // A negative value is indicated by adding 0x80 (128) to the temperature value + const float temperature = ((response[12] + (response[13] * 0.1)) > 128) + ? (((response[12] + (response[13] * 0.1)) - 128) * -1) + : response[12] + (response[13] * 0.1); const float humidity = response[14] + (response[15] * 0.1); ESP_LOGD(TAG, "Received CO₂: %u ppm", co2); From 5e345783bdc68773c81012388bf5891c2f229ed4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 09:55:18 +0200 Subject: [PATCH 262/285] Fix docker release deploy push flag (#2348) --- docker/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/build.py b/docker/build.py index 1904457989..cd8dca3b87 100755 --- a/docker/build.py +++ b/docker/build.py @@ -31,7 +31,7 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) build_parser = subparsers.add_parser("build", help="Build the image") -build_parser.add_argument("--push", help="Also push the images") +build_parser.add_argument("--push", help="Also push the images", action="store_true") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") From 3052c64dd707139b5b4ccca478915423d54d10e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Srebrni=C4=8D?= Date: Mon, 20 Sep 2021 10:08:08 +0200 Subject: [PATCH 263/285] Add invert_colors option for st7735 (#2327) --- esphome/components/st7735/display.py | 3 +++ esphome/components/st7735/st7735.cpp | 7 ++++++- esphome/components/st7735/st7735.h | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index c1ede4e0ce..ae31f604a5 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -23,6 +23,7 @@ CONF_ROW_START = "row_start" CONF_COL_START = "col_start" CONF_EIGHT_BIT_COLOR = "eight_bit_color" CONF_USE_BGR = "use_bgr" +CONF_INVERT_COLORS = "invert_colors" SPIST7735 = st7735_ns.class_( "ST7735", cg.PollingComponent, display.DisplayBuffer, spi.SPIDevice @@ -58,6 +59,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_ROW_START): cv.int_, cv.Optional(CONF_EIGHT_BIT_COLOR, default=False): cv.boolean, cv.Optional(CONF_USE_BGR, default=False): cv.boolean, + cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, } ) .extend(cv.COMPONENT_SCHEMA) @@ -90,6 +92,7 @@ async def to_code(config): config[CONF_ROW_START], config[CONF_EIGHT_BIT_COLOR], config[CONF_USE_BGR], + config[CONF_INVERT_COLORS], ) await setup_st7735(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index b44f76a9e6..1f14136637 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -220,12 +220,14 @@ static const uint8_t PROGMEM // clang-format on static const char *const TAG = "st7735"; -ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr) +ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr, + bool invert_colors) : model_(model), colstart_(colstart), rowstart_(rowstart), eightbitcolor_(eightbitcolor), usebgr_(usebgr), + invert_colors_(invert_colors), width_(width), height_(height) {} @@ -281,6 +283,9 @@ void ST7735::setup() { } sendcommand_(ST77XX_MADCTL, &data, 1); + if (this->invert_colors_) + sendcommand_(ST77XX_INVON, nullptr, 0); + this->init_internal_(this->get_buffer_length()); memset(this->buffer_, 0x00, this->get_buffer_length()); } diff --git a/esphome/components/st7735/st7735.h b/esphome/components/st7735/st7735.h index 0eb4ccadd8..c049fb9e83 100644 --- a/esphome/components/st7735/st7735.h +++ b/esphome/components/st7735/st7735.h @@ -37,7 +37,8 @@ class ST7735 : public PollingComponent, public spi::SPIDevice { public: - ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr); + ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr, + bool invert_colors); void dump_config() override; void setup() override; @@ -77,6 +78,7 @@ class ST7735 : public PollingComponent, uint8_t colstart_ = 0, rowstart_ = 0; bool eightbitcolor_ = false; bool usebgr_ = false; + bool invert_colors_ = false; int16_t width_ = 80, height_ = 80; // Watch heap size GPIOPin *reset_pin_{nullptr}; From 9ebe075f9b85ff5940748df3ce393bccc1e72c88 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Mon, 20 Sep 2021 10:12:32 +0200 Subject: [PATCH 264/285] Add deep sleep wakeup from touch (#1238) (#2281) --- esphome/components/deep_sleep/__init__.py | 5 ++++ .../deep_sleep/deep_sleep_component.cpp | 7 ++++++ .../deep_sleep/deep_sleep_component.h | 3 +++ .../components/esp32_touch/binary_sensor.py | 3 +++ .../components/esp32_touch/esp32_touch.cpp | 25 ++++++++++++++++--- esphome/components/esp32_touch/esp32_touch.h | 4 ++- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 3f59ad52cf..1e07b75173 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -44,6 +44,7 @@ EXT1_WAKEUP_MODES = { CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" +CONF_TOUCH_WAKEUP = "touch_wakeup" CONFIG_SCHEMA = cv.Schema( { @@ -67,6 +68,7 @@ CONFIG_SCHEMA = cv.Schema( } ), ), + cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean), } ).extend(cv.COMPONENT_SCHEMA) @@ -95,6 +97,9 @@ async def to_code(config): ) cg.add(var.set_ext1_wakeup(struct)) + if CONF_TOUCH_WAKEUP in config: + cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP])) + cg.add_define("USE_DEEP_SLEEP") diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 00f660f41a..9c354c8513 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -44,6 +44,7 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } +void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } #endif void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { @@ -80,6 +81,12 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->ext1_wakeup_.has_value()) { esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode); } + + if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) { + esp_sleep_enable_touchpad_wakeup(); + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + } + esp_deep_sleep_start(); #endif diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 5050bd6b4d..575f7be72b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -53,6 +53,8 @@ class DeepSleepComponent : public Component { void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); + + void set_touch_wakeup(bool touch_wakeup); #endif /// Set a duration in ms for how long the code should run before entering deep sleep mode. void set_run_duration(uint32_t time_ms); @@ -74,6 +76,7 @@ class DeepSleepComponent : public Component { optional wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; + optional touch_wakeup_; #endif optional run_duration_; bool next_enter_deep_sleep_{false}; diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 300de23f08..198a43a738 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -15,6 +15,7 @@ ESP_PLATFORMS = [ESP_PLATFORM_ESP32] DEPENDENCIES = ["esp32_touch"] CONF_ESP32_TOUCH_ID = "esp32_touch_id" +CONF_WAKEUP_THRESHOLD = "wakeup_threshold" TOUCH_PADS = { 4: cg.global_ns.TOUCH_PAD_NUM0, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), cv.Required(CONF_PIN): validate_touch_pad, cv.Required(CONF_THRESHOLD): cv.uint16_t, + cv.Optional(CONF_WAKEUP_THRESHOLD, default=0): cv.uint16_t, } ) @@ -58,6 +60,7 @@ async def to_code(config): config[CONF_NAME], TOUCH_PADS[config[CONF_PIN]], config[CONF_THRESHOLD], + config[CONF_WAKEUP_THRESHOLD], ) await binary_sensor.register_binary_sensor(var, config) cg.add(hub.register_touch_pad(var)) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index a990e632af..9ae671a4a9 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -133,15 +133,34 @@ void ESP32TouchComponent::loop() { } void ESP32TouchComponent::on_shutdown() { + bool is_wakeup_source = false; + if (this->iir_filter_enabled_()) { touch_pad_filter_stop(); touch_pad_filter_delete(); } - touch_pad_deinit(); + + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + } + + // No filter available when using as wake-up source. + touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); + } + } + + if (!is_wakeup_source) { + touch_pad_deinit(); + } } -ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold) - : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold) {} +ESP32TouchBinarySensor::ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, + uint16_t wakeup_threshold) + : BinarySensor(name), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 45d459a2ff..8b92a482c0 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -57,12 +57,13 @@ class ESP32TouchComponent : public Component { /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold); + ESP32TouchBinarySensor(const std::string &name, touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); touch_pad_t get_touch_pad() const { return touch_pad_; } uint16_t get_threshold() const { return threshold_; } void set_threshold(uint16_t threshold) { threshold_ = threshold; } uint16_t get_value() const { return value_; } + uint16_t get_wakeup_threshold() const { return wakeup_threshold_; } protected: friend ESP32TouchComponent; @@ -70,6 +71,7 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t touch_pad_; uint16_t threshold_; uint16_t value_; + const uint16_t wakeup_threshold_; }; } // namespace esp32_touch From 7452ef23b11575b2f8097f811f64015ec121eae2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Sep 2021 10:16:59 +0200 Subject: [PATCH 265/285] Add ESPHOME_VERSION_CODE define (#2324) --- esphome/core/version.h | 3 +++ esphome/writer.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/core/version.h b/esphome/core/version.h index b64f581b25..7336ebf7f8 100644 --- a/esphome/core/version.h +++ b/esphome/core/version.h @@ -6,4 +6,7 @@ // // This file is only used by static analyzers and IDEs. +#include "esphome/core/macros.h" + #define ESPHOME_VERSION "dev" +#define ESPHOME_VERSION_CODE VERSION_CODE(2099, 12, 0) diff --git a/esphome/writer.py b/esphome/writer.py index 19953916c7..2e1e19de54 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -342,7 +342,9 @@ DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ """ VERSION_H_FORMAT = """\ #pragma once +#include "esphome/core/macros.h" #define ESPHOME_VERSION "{}" +#define ESPHOME_VERSION_CODE VERSION_CODE({}, {}, {}) """ DEFINES_H_TARGET = "esphome/core/defines.h" VERSION_H_TARGET = "esphome/core/version.h" @@ -415,8 +417,7 @@ def copy_src_tree(): CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) ) write_file_if_changed( - CORE.relative_src_path("esphome", "core", "version.h"), - VERSION_H_FORMAT.format(__version__), + CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) @@ -426,6 +427,15 @@ def generate_defines_h(): return DEFINES_H_FORMAT.format("\n".join(define_content_l)) +def generate_version_h(): + match = re.match(r"^(\d+)\.(\d+).(\d+)-?\w*$", __version__) + if not match: + raise EsphomeError(f"Could not parse version {__version__}.") + return VERSION_H_FORMAT.format( + __version__, match.group(1), match.group(2), match.group(3) + ) + + def write_cpp(code_s): path = CORE.relative_src_path("main.cpp") if os.path.isfile(path): From 2d7f8b3bdfc664478958af77270c27e90f845d4f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 10:31:48 +0200 Subject: [PATCH 266/285] Install python requirements after apt ones for better caching (#2349) * Install python requirements after apt ones for better caching * Fix buildkit caching works differently --- docker/Dockerfile | 21 ++++++++++++++++----- docker/build.py | 10 +++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d3ee219de8..9c3e864e43 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -51,17 +51,17 @@ RUN \ && platformio settings set check_platforms_interval 1000000 \ && mkdir -p /piolibs + + +# ======================= docker-type image ======================= +FROM base AS docker + # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini - - -# ======================= docker-type image ======================= -FROM base AS docker - # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir -e /esphome @@ -104,6 +104,12 @@ ARG BUILD_VERSION=dev # Copy root filesystem COPY docker/hassio-rootfs/ / +# First install requirements to leverage caching when requirements don't change +COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ + && /platformio_install_deps.py /platformio.ini + # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir -e /esphome @@ -141,5 +147,10 @@ RUN \ /var/{cache,log}/* \ /var/lib/apt/lists/* +COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +RUN \ + pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ + && /platformio_install_deps.py /platformio.ini + VOLUME ["/esphome"] WORKDIR /esphome diff --git a/docker/build.py b/docker/build.py index cd8dca3b87..1157d8287a 100755 --- a/docker/build.py +++ b/docker/build.py @@ -109,9 +109,9 @@ def main(): # 1. pull cache image params = DockerParams.for_type_arch(args.build_type, args.arch) cache_tag = { - CHANNEL_DEV: "dev", - CHANNEL_BETA: "beta", - CHANNEL_RELEASE: "latest", + CHANNEL_DEV: "cache-dev", + CHANNEL_BETA: "cache-beta", + CHANNEL_RELEASE: "cache-latest", }[channel] cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" @@ -123,7 +123,7 @@ def main(): "docker", "buildx", "build", "--build-arg", f"BASEIMGTYPE={params.baseimgtype}", "--build-arg", f"BUILD_VERSION={args.tag}", - "--cache-from", cache_img, + "--cache-from", f"type=registry,ref={cache_img}", "--file", "docker/Dockerfile", "--platform", params.platform, "--target", params.target, @@ -131,7 +131,7 @@ def main(): for img in imgs: cmd += ["--tag", img] if args.push: - cmd.append("--push") + cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] run_command(*cmd, ".") elif args.command == "manifest": From 1e8e471dec19ceafba1997b1d9663f7912f244a2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Sep 2021 11:16:31 +0200 Subject: [PATCH 267/285] Introduce call_dump_config() indirection (#2325) --- esphome/components/mqtt/mqtt_component.cpp | 6 ++++++ esphome/components/mqtt/mqtt_component.h | 2 ++ esphome/core/application.cpp | 2 +- esphome/core/component.cpp | 3 ++- esphome/core/component.h | 4 ++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 6e376d0fef..3ed9aafb42 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -186,6 +186,12 @@ void MQTTComponent::call_loop() { this->schedule_resend_state(); } } +void MQTTComponent::call_dump_config() { + if (this->is_internal()) + return; + + this->dump_config(); +} void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } std::string MQTTComponent::unique_id() { return ""; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 83a0c06644..668162da5a 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -62,6 +62,8 @@ class MQTTComponent : public Component { void call_loop() override; + void call_dump_config() override; + /// Send discovery info the Home Assistant, override this. virtual void send_discovery(JsonObject &root, SendDiscoveryConfig &config) = 0; diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 5ab1d973f4..d59ad23a5e 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -105,7 +105,7 @@ void Application::loop() { #endif } - this->components_[this->dump_config_at_]->dump_config(); + this->components_[this->dump_config_at_]->call_dump_config(); this->dump_config_at_++; } } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 7682c083a5..e3b32978cd 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -64,8 +64,9 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT } void Component::call_loop() { this->loop(); } - void Component::call_setup() { this->setup(); } +void Component::call_dump_config() { this->dump_config(); } + uint32_t Component::get_component_state() const { return this->component_state_; } void Component::call() { uint32_t state = this->component_state_ & COMPONENT_STATE_MASK; diff --git a/esphome/core/component.h b/esphome/core/component.h index ea87ebcdfe..2654504fe8 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -148,8 +148,12 @@ class Component { const char *get_component_source() const; protected: + friend class Application; + virtual void call_loop(); virtual void call_setup(); + virtual void call_dump_config(); + /** Set an interval function with a unique name. Empty name means no cancelling possible. * * This will call f every interval ms. Can be cancelled via CancelInterval(). From ac0d921413c3884752193fe568fa82853f0f99e9 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 11:47:51 +0200 Subject: [PATCH 268/285] ESP-IDF support and generic target platforms (#2303) * Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +- .gitignore | 2 + CODEOWNERS | 3 + esphome/__main__.py | 2 +- esphome/codegen.py | 3 + esphome/components/a4988/a4988.h | 2 +- esphome/components/ac_dimmer/ac_dimmer.cpp | 47 +- esphome/components/ac_dimmer/ac_dimmer.h | 20 +- esphome/components/ac_dimmer/output.py | 25 +- esphome/components/adc/adc_sensor.cpp | 29 +- esphome/components/adc/adc_sensor.h | 14 +- esphome/components/adc/sensor.py | 37 +- esphome/components/ade7953/ade7953.cpp | 33 +- esphome/components/ade7953/ade7953.h | 75 +- esphome/components/ade7953/sensor.py | 5 +- esphome/components/ads1115/ads1115.cpp | 3 +- esphome/components/aht10/aht10.cpp | 44 +- .../airthings_ble/airthings_listener.cpp | 2 +- .../airthings_ble/airthings_listener.h | 3 +- .../airthings_wave_plus.cpp | 4 +- .../airthings_wave_plus/airthings_wave_plus.h | 4 +- .../components/airthings_wave_plus/sensor.py | 6 +- esphome/components/am2320/am2320.cpp | 3 +- esphome/components/am43/am43.cpp | 3 +- esphome/components/am43/am43.h | 2 +- esphome/components/am43/cover/am43_cover.cpp | 2 +- esphome/components/am43/cover/am43_cover.h | 2 +- esphome/components/anova/anova.cpp | 2 +- esphome/components/anova/anova.h | 2 +- esphome/components/apds9960/apds9960.cpp | 1 + esphome/components/api/api_connection.cpp | 5 +- esphome/components/api/api_frame_helper.cpp | 1 + esphome/components/api/api_pb2.cpp | 1 + esphome/components/api/api_server.cpp | 4 +- esphome/components/api/proto.h | 1 + esphome/components/as3935/as3935.h | 2 +- esphome/components/as3935_i2c/as3935_i2c.cpp | 8 +- esphome/components/async_tcp/__init__.py | 6 + .../atc_mithermometer/atc_mithermometer.cpp | 2 +- .../atc_mithermometer/atc_mithermometer.h | 2 +- esphome/components/b_parasite/b_parasite.cpp | 4 +- esphome/components/b_parasite/b_parasite.h | 4 +- .../bang_bang/bang_bang_climate.cpp | 3 +- esphome/components/bh1750/bh1750.cpp | 3 +- esphome/components/binary_sensor/automation.h | 1 + esphome/components/ble_client/automation.h | 2 +- esphome/components/ble_client/ble_client.cpp | 2 +- esphome/components/ble_client/ble_client.h | 2 +- .../components/ble_client/sensor/automation.h | 2 +- .../ble_client/sensor/ble_sensor.cpp | 2 +- .../components/ble_client/sensor/ble_sensor.h | 2 +- .../ble_client/switch/ble_switch.cpp | 2 +- .../components/ble_client/switch/ble_switch.h | 2 +- .../ble_presence/ble_presence_device.cpp | 2 +- .../ble_presence/ble_presence_device.h | 2 +- .../components/ble_rssi/ble_rssi_sensor.cpp | 2 +- esphome/components/ble_rssi/ble_rssi_sensor.h | 2 +- .../components/ble_scanner/ble_scanner.cpp | 2 +- esphome/components/ble_scanner/ble_scanner.h | 2 +- esphome/components/bme280/bme280.cpp | 2 +- esphome/components/bme680/bme680.cpp | 1 + .../components/bme680_bsec/bme680_bsec.cpp | 2 +- esphome/components/bmp280/bmp280.cpp | 2 +- esphome/components/captive_portal/__init__.py | 19 +- .../captive_portal/captive_portal.cpp | 14 +- .../captive_portal/captive_portal.h | 4 + esphome/components/ccs811/ccs811.cpp | 5 +- esphome/components/climate/climate.cpp | 16 +- esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/cover/cover.cpp | 2 +- .../components/ct_clamp/ct_clamp_sensor.cpp | 2 +- esphome/components/ct_clamp/ct_clamp_sensor.h | 2 +- .../components/dallas/dallas_component.cpp | 2 +- esphome/components/dallas/esp_one_wire.cpp | 38 +- esphome/components/dallas/esp_one_wire.h | 3 +- esphome/components/debug/debug_component.cpp | 32 +- esphome/components/deep_sleep/__init__.py | 2 +- .../deep_sleep/deep_sleep_component.cpp | 30 +- .../deep_sleep/deep_sleep_component.h | 16 +- esphome/components/demo/demo_sensor.h | 2 +- esphome/components/dht/dht.cpp | 6 +- esphome/components/dht/dht.h | 6 +- esphome/components/display/display_buffer.cpp | 23 +- esphome/components/display/display_buffer.h | 1 + esphome/components/dsmr/__init__.py | 19 +- esphome/components/dsmr/dsmr.cpp | 7 +- esphome/components/dsmr/dsmr.h | 4 + .../duty_cycle/duty_cycle_sensor.cpp | 6 +- .../components/duty_cycle/duty_cycle_sensor.h | 8 +- esphome/components/duty_cycle/sensor.py | 4 +- esphome/components/e131/__init__.py | 17 +- esphome/components/e131/e131.cpp | 8 +- esphome/components/e131/e131.h | 4 + .../e131/e131_addressable_light_effect.cpp | 4 + .../e131/e131_addressable_light_effect.h | 4 + esphome/components/e131/e131_packet.cpp | 14 +- esphome/components/endstop/endstop_cover.cpp | 1 + esphome/components/esp32/__init__.py | 379 ++++++++ esphome/{ => components/esp32}/boards.py | 225 ----- esphome/components/esp32/const.py | 21 + esphome/components/esp32/core.cpp | 89 ++ esphome/components/esp32/gpio.py | 201 ++++ esphome/components/esp32/gpio_arduino.cpp | 107 +++ esphome/components/esp32/gpio_arduino.h | 36 + esphome/components/esp32/gpio_idf.cpp | 49 + esphome/components/esp32/gpio_idf.h | 96 ++ esphome/components/esp32/preferences.cpp | 99 ++ esphome/components/esp32/preferences.h | 12 + esphome/components/esp32_ble/__init__.py | 9 +- esphome/components/esp32_ble/ble.cpp | 36 +- esphome/components/esp32_ble/ble.h | 2 +- .../components/esp32_ble/ble_advertising.cpp | 5 +- .../components/esp32_ble/ble_advertising.h | 2 +- esphome/components/esp32_ble/ble_uuid.cpp | 6 +- esphome/components/esp32_ble/ble_uuid.h | 4 +- esphome/components/esp32_ble/queue.h | 8 +- .../components/esp32_ble_beacon/__init__.py | 9 +- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 33 +- .../esp32_ble_beacon/esp32_ble_beacon.h | 2 +- .../components/esp32_ble_server/__init__.py | 9 +- .../components/esp32_ble_server/ble_2901.cpp | 2 +- .../components/esp32_ble_server/ble_2901.h | 2 +- .../components/esp32_ble_server/ble_2902.cpp | 4 +- .../components/esp32_ble_server/ble_2902.h | 2 +- .../esp32_ble_server/ble_characteristic.cpp | 2 +- .../esp32_ble_server/ble_characteristic.h | 4 +- .../esp32_ble_server/ble_descriptor.cpp | 5 +- .../esp32_ble_server/ble_descriptor.h | 2 +- .../esp32_ble_server/ble_server.cpp | 2 +- .../components/esp32_ble_server/ble_server.h | 2 +- .../esp32_ble_server/ble_service.cpp | 2 +- .../components/esp32_ble_server/ble_service.h | 2 +- .../components/esp32_ble_tracker/__init__.py | 8 +- .../components/esp32_ble_tracker/automation.h | 2 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 41 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 2 +- esphome/components/esp32_ble_tracker/queue.h | 7 +- esphome/components/esp32_camera/__init__.py | 31 +- .../components/esp32_camera/esp32_camera.cpp | 7 +- .../components/esp32_camera/esp32_camera.h | 4 +- esphome/components/esp32_dac/esp32_dac.cpp | 19 +- esphome/components/esp32_dac/esp32_dac.h | 8 +- esphome/components/esp32_dac/output.py | 4 +- esphome/components/esp32_hall/esp32_hall.cpp | 10 +- esphome/components/esp32_hall/esp32_hall.h | 2 +- esphome/components/esp32_hall/sensor.py | 3 +- esphome/components/esp32_improv/__init__.py | 5 +- .../esp32_improv/esp32_improv_component.cpp | 2 +- .../esp32_improv/esp32_improv_component.h | 2 +- esphome/components/esp32_touch/__init__.py | 3 +- .../components/esp32_touch/binary_sensor.py | 8 +- .../components/esp32_touch/esp32_touch.cpp | 5 +- esphome/components/esp32_touch/esp32_touch.h | 9 +- esphome/components/esp8266/__init__.py | 213 +++++ esphome/components/esp8266/boards.py | 266 ++++++ esphome/components/esp8266/const.py | 8 + esphome/components/esp8266/core.cpp | 37 + esphome/components/esp8266/gpio.cpp | 96 ++ esphome/components/esp8266/gpio.h | 38 + esphome/components/esp8266/gpio.py | 170 ++++ esphome/components/esp8266/preferences.cpp | 263 +++++ esphome/components/esp8266/preferences.h | 14 + .../components/esp8266_pwm/esp8266_pwm.cpp | 4 +- esphome/components/esp8266_pwm/esp8266_pwm.h | 8 +- esphome/components/esp8266_pwm/output.py | 3 +- esphome/components/ethernet/__init__.py | 20 +- .../ethernet/ethernet_component.cpp | 24 +- .../components/ethernet/ethernet_component.h | 21 +- .../exposure_notifications.cpp | 2 +- .../exposure_notifications.h | 2 +- esphome/components/ezo/ezo.cpp | 5 +- esphome/components/fan/fan_state.cpp | 2 +- .../components/fastled_base/fastled_light.cpp | 4 + .../components/fastled_base/fastled_light.h | 4 + esphome/components/fastled_clockless/light.py | 3 +- esphome/components/fastled_spi/light.py | 17 +- .../fingerprint_grow/fingerprint_grow.cpp | 2 +- esphome/components/globals/__init__.py | 12 +- .../components/globals/globals_component.h | 40 +- .../gpio/binary_sensor/gpio_binary_sensor.h | 2 +- .../gpio/output/gpio_binary_output.h | 2 +- esphome/components/gpio/switch/gpio_switch.h | 2 +- esphome/components/gps/__init__.py | 5 +- esphome/components/gps/gps.cpp | 4 + esphome/components/gps/gps.h | 4 + esphome/components/gps/time/gps_time.cpp | 4 + esphome/components/gps/time/gps_time.h | 4 + esphome/components/graph/graph.cpp | 30 +- esphome/components/hdc1080/hdc1080.cpp | 17 +- esphome/components/hlw8012/hlw8012.h | 10 +- esphome/components/hlw8012/sensor.py | 8 +- .../hm3301/abstract_aqi_calculator.h | 5 + esphome/components/hm3301/aqi_calculator.h | 4 + .../hm3301/aqi_calculator_factory.h | 4 + esphome/components/hm3301/caqi_calculator.h | 4 + esphome/components/hm3301/hm3301.cpp | 4 + esphome/components/hm3301/hm3301.h | 4 + esphome/components/hm3301/sensor.py | 1 + esphome/components/http_request/__init__.py | 50 +- .../components/http_request/http_request.cpp | 10 +- .../components/http_request/http_request.h | 10 +- esphome/components/htu21d/htu21d.cpp | 17 +- esphome/components/hx711/hx711.h | 2 +- esphome/components/i2c/__init__.py | 71 +- esphome/components/i2c/i2c.cpp | 291 +----- esphome/components/i2c/i2c.h | 284 ++---- esphome/components/i2c/i2c_bus.h | 46 + esphome/components/i2c/i2c_bus_arduino.cpp | 97 ++ esphome/components/i2c/i2c_bus_arduino.h | 37 + esphome/components/i2c/i2c_bus_esp_idf.cpp | 147 +++ esphome/components/i2c/i2c_bus_esp_idf.h | 41 + .../components/ili9341/ili9341_display.cpp | 5 +- esphome/components/improv/improv.cpp | 2 + esphome/components/improv/improv.h | 5 + esphome/components/ina219/ina219.cpp | 10 +- esphome/components/ina226/ina226.cpp | 10 +- esphome/components/ina3221/ina3221.cpp | 5 +- .../inkbird_ibsth1_mini.cpp | 6 +- .../inkbird_ibsth1_mini/inkbird_ibsth1_mini.h | 2 +- esphome/components/inkplate6/display.py | 5 +- esphome/components/inkplate6/inkplate.cpp | 162 ++-- esphome/components/inkplate6/inkplate.h | 67 +- .../integration/integration_sensor.cpp | 3 +- .../integration/integration_sensor.h | 1 + esphome/components/json/__init__.py | 6 + esphome/components/json/json_util.cpp | 4 + esphome/components/json/json_util.h | 4 + esphome/components/lcd_base/lcd_display.cpp | 1 + .../components/lcd_gpio/gpio_lcd_display.cpp | 11 +- .../components/lcd_gpio/gpio_lcd_display.h | 2 +- .../lcd_pcf8574/pcf8574_display.cpp | 1 + esphome/components/ledc/ledc_output.cpp | 107 ++- esphome/components/ledc/ledc_output.h | 8 +- esphome/components/ledc/output.py | 3 +- esphome/components/light/light_state.cpp | 2 +- esphome/components/light/light_transformer.h | 3 + esphome/components/logger/__init__.py | 2 +- esphome/components/logger/logger.cpp | 74 +- esphome/components/logger/logger.h | 27 +- esphome/components/max31856/max31856.cpp | 4 +- esphome/components/max31856/max31856.h | 4 +- esphome/components/max31865/max31865.cpp | 6 +- esphome/components/max31865/max31865.h | 6 +- esphome/components/max7219/max7219.cpp | 3 +- .../components/max7219digit/max7219digit.cpp | 3 +- .../components/max7219digit/max7219digit.h | 4 +- esphome/components/max7219digit/max7219font.h | 4 +- esphome/components/mcp23008/mcp23008.h | 2 +- esphome/components/mcp23016/__init__.py | 61 +- esphome/components/mcp23016/mcp23016.cpp | 27 +- esphome/components/mcp23016/mcp23016.h | 23 +- esphome/components/mcp23017/mcp23017.h | 2 +- esphome/components/mcp23s08/mcp23s08.h | 2 +- esphome/components/mcp23s17/mcp23s17.h | 2 +- .../mcp23x08_base/mcp23x08_base.cpp | 22 +- .../components/mcp23x08_base/mcp23x08_base.h | 4 +- .../mcp23x17_base/mcp23x17_base.cpp | 22 +- .../components/mcp23x17_base/mcp23x17_base.h | 4 +- esphome/components/mcp23xxx_base/__init__.py | 79 +- .../mcp23xxx_base/mcp23xxx_base.cpp | 15 +- .../components/mcp23xxx_base/mcp23xxx_base.h | 26 +- esphome/components/mcp3008/mcp3008.h | 2 +- esphome/components/mcp4725/mcp4725.cpp | 7 +- esphome/components/mcp9808/mcp9808.cpp | 10 +- esphome/components/mdns/__init__.py | 25 + esphome/components/mdns/mdns_component.cpp | 74 ++ esphome/components/mdns/mdns_component.h | 37 + .../components/mdns/mdns_esp32_arduino.cpp | 27 + esphome/components/mdns/mdns_esp8266.cpp | 32 + esphome/components/mdns/mdns_esp_idf.cpp | 52 + esphome/components/midea/adapter.cpp | 4 + esphome/components/midea/adapter.h | 5 + esphome/components/midea/air_conditioner.cpp | 4 + esphome/components/midea/air_conditioner.h | 5 + esphome/components/midea/appliance_base.h | 14 +- esphome/components/midea/automations.h | 5 + esphome/components/midea/climate.py | 3 +- esphome/components/midea/midea_ir.h | 3 + esphome/components/mpr121/mpr121.cpp | 1 + esphome/components/mqtt/__init__.py | 1 + .../components/mqtt/custom_mqtt_device.cpp | 5 + esphome/components/mqtt/custom_mqtt_device.h | 5 + .../components/mqtt/mqtt_binary_sensor.cpp | 2 + esphome/components/mqtt/mqtt_binary_sensor.h | 3 +- esphome/components/mqtt/mqtt_client.cpp | 44 +- esphome/components/mqtt/mqtt_client.h | 12 +- esphome/components/mqtt/mqtt_climate.cpp | 4 +- esphome/components/mqtt/mqtt_climate.h | 2 + esphome/components/mqtt/mqtt_component.cpp | 5 + esphome/components/mqtt/mqtt_component.h | 6 + esphome/components/mqtt/mqtt_cover.cpp | 2 + esphome/components/mqtt/mqtt_cover.h | 2 + esphome/components/mqtt/mqtt_fan.cpp | 2 + esphome/components/mqtt/mqtt_fan.h | 2 + esphome/components/mqtt/mqtt_light.cpp | 2 + esphome/components/mqtt/mqtt_light.h | 2 + esphome/components/mqtt/mqtt_number.cpp | 2 + esphome/components/mqtt/mqtt_number.h | 2 + esphome/components/mqtt/mqtt_select.cpp | 2 + esphome/components/mqtt/mqtt_select.h | 2 + esphome/components/mqtt/mqtt_sensor.cpp | 2 + esphome/components/mqtt/mqtt_sensor.h | 2 + esphome/components/mqtt/mqtt_switch.cpp | 2 + esphome/components/mqtt/mqtt_switch.h | 2 + esphome/components/mqtt/mqtt_text_sensor.cpp | 2 + esphome/components/mqtt/mqtt_text_sensor.h | 2 + .../sensor/mqtt_subscribe_sensor.cpp | 5 + .../sensor/mqtt_subscribe_sensor.h | 6 + .../mqtt_subscribe_text_sensor.cpp | 4 + .../text_sensor/mqtt_subscribe_text_sensor.h | 6 + esphome/components/ms5611/ms5611.cpp | 1 + esphome/components/my9231/my9231.h | 3 +- esphome/components/neopixelbus/light.py | 7 +- .../neopixelbus/neopixelbus_light.h | 6 +- esphome/components/network/__init__.py | 12 +- esphome/components/network/ip_address.h | 45 + esphome/components/network/util.cpp | 54 ++ esphome/components/network/util.h | 16 + esphome/components/nextion/display.py | 4 +- esphome/components/nextion/nextion.h | 19 +- esphome/components/nextion/nextion_upload.cpp | 32 +- .../nextion/sensor/nextion_sensor.cpp | 2 +- esphome/components/ntc/ntc.cpp | 2 +- esphome/components/number/automation.cpp | 10 +- esphome/components/number/automation.h | 4 +- esphome/components/number/number.cpp | 2 +- esphome/components/ota/__init__.py | 18 +- esphome/components/ota/ota_component.cpp | 432 ++++++--- esphome/components/ota/ota_component.h | 27 +- esphome/components/pca9685/pca9685_output.h | 4 +- esphome/components/pcf8574/__init__.py | 61 +- esphome/components/pcf8574/pcf8574.cpp | 36 +- esphome/components/pcf8574/pcf8574.h | 23 +- esphome/components/pid/pid_autotuner.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 2 +- esphome/components/pid/pid_controller.h | 6 +- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/pipsolar/pipsolar.h | 2 +- esphome/components/pmsa003i/pmsa003i.cpp | 1 + esphome/components/pn532/pn532.cpp | 1 + esphome/components/pn532_i2c/pn532_i2c.cpp | 3 +- .../components/power_supply/power_supply.h | 2 +- .../prometheus/prometheus_handler.cpp | 8 +- .../prometheus/prometheus_handler.h | 4 + .../pulse_counter/pulse_counter_sensor.cpp | 14 +- .../pulse_counter/pulse_counter_sensor.h | 24 +- .../pulse_meter/pulse_meter_sensor.cpp | 6 +- .../pulse_meter/pulse_meter_sensor.h | 8 +- .../components/pulse_width/pulse_width.cpp | 4 +- esphome/components/pulse_width/pulse_width.h | 12 +- esphome/components/pulse_width/sensor.py | 4 +- .../pvvx_mithermometer/pvvx_mithermometer.cpp | 2 +- .../pvvx_mithermometer/pvvx_mithermometer.h | 2 +- esphome/components/qmc5883l/qmc5883l.cpp | 7 +- esphome/components/rc522/rc522.cpp | 10 +- esphome/components/rc522/rc522.h | 2 +- esphome/components/rc522_i2c/rc522_i2c.cpp | 5 +- esphome/components/remote_base/__init__.py | 7 +- .../components/remote_base/midea_protocol.h | 7 +- .../components/remote_base/remote_base.cpp | 2 +- esphome/components/remote_base/remote_base.h | 14 +- .../remote_base/samsung_protocol.cpp | 1 + .../remote_base/toshiba_ac_protocol.cpp | 1 + .../components/remote_receiver/__init__.py | 4 +- .../remote_receiver/remote_receiver.h | 16 +- .../remote_receiver/remote_receiver_esp32.cpp | 2 +- .../remote_receiver_esp8266.cpp | 10 +- .../remote_transmitter/remote_transmitter.h | 8 +- .../remote_transmitter_esp32.cpp | 2 +- .../remote_transmitter_esp8266.cpp | 2 +- .../resistance/resistance_sensor.cpp | 2 +- esphome/components/restart/restart_switch.cpp | 1 + .../rotary_encoder/rotary_encoder.cpp | 23 +- .../rotary_encoder/rotary_encoder.h | 14 +- esphome/components/rotary_encoder/sensor.py | 8 +- esphome/components/ruuvi_ble/ruuvi_ble.cpp | 2 +- esphome/components/ruuvi_ble/ruuvi_ble.h | 2 +- esphome/components/ruuvitag/ruuvitag.cpp | 2 +- esphome/components/ruuvitag/ruuvitag.h | 2 +- esphome/components/scd30/scd30.cpp | 17 +- esphome/components/sdp3x/sdp3x.cpp | 14 +- esphome/components/sensor/automation.h | 14 +- esphome/components/sensor/filter.cpp | 19 +- esphome/components/servo/servo.cpp | 1 + esphome/components/servo/servo.h | 2 +- esphome/components/sgp30/sgp30.cpp | 9 +- esphome/components/sgp40/sgp40.cpp | 15 +- esphome/components/sht3xd/sht3xd.cpp | 2 +- esphome/components/sht4x/sht4x.cpp | 4 +- esphome/components/shtcx/shtcx.cpp | 3 +- .../components/shutdown/shutdown_switch.cpp | 11 +- esphome/components/slow_pwm/slow_pwm_output.h | 2 +- esphome/components/sm16716/sm16716.h | 3 +- esphome/components/sm2135/sm2135.h | 3 +- esphome/components/sn74hc595/__init__.py | 33 +- esphome/components/sn74hc595/sn74hc595.cpp | 26 +- esphome/components/sn74hc595/sn74hc595.h | 16 +- esphome/components/sntp/sntp_component.cpp | 8 +- .../components/socket/bsd_sockets_impl.cpp | 13 +- esphome/components/socket/headers.h | 8 +- .../components/socket/lwip_raw_tcp_impl.cpp | 3 +- esphome/components/spi/spi.cpp | 73 +- esphome/components/spi/spi.h | 43 +- esphome/components/sps30/sps30.cpp | 2 +- .../components/ssd1306_base/ssd1306_base.h | 2 +- .../components/ssd1306_i2c/ssd1306_i2c.cpp | 4 +- .../components/ssd1322_base/ssd1322_base.h | 2 +- .../components/ssd1325_base/ssd1325_base.h | 2 +- .../components/ssd1327_base/ssd1327_base.h | 2 +- .../components/ssd1327_i2c/ssd1327_i2c.cpp | 4 +- .../components/ssd1331_base/ssd1331_base.h | 2 +- .../components/ssd1351_base/ssd1351_base.h | 2 +- esphome/components/st7735/st7735.cpp | 17 +- .../status/status_binary_sensor.cpp | 4 +- .../status_led/light/status_led_light.h | 2 +- esphome/components/status_led/status_led.h | 2 +- esphome/components/stepper/stepper.cpp | 1 + esphome/components/sts3x/sts3x.cpp | 2 +- esphome/components/sun/sun.h | 4 +- esphome/components/switch/switch.cpp | 2 +- esphome/components/sx1509/__init__.py | 68 +- .../sx1509/output/sx1509_float_output.cpp | 3 +- esphome/components/sx1509/sx1509.cpp | 51 +- esphome/components/sx1509/sx1509.h | 22 +- esphome/components/sx1509/sx1509_gpio_pin.cpp | 13 +- esphome/components/sx1509/sx1509_gpio_pin.h | 13 +- esphome/components/tca9548a/__init__.py | 37 +- esphome/components/tca9548a/tca9548a.cpp | 43 +- esphome/components/tca9548a/tca9548a.h | 24 +- esphome/components/tcs34725/tcs34725.cpp | 1 + .../template/number/template_number.cpp | 4 +- .../template/select/template_select.cpp | 2 +- .../template/sensor/template_sensor.cpp | 3 +- .../thermostat/thermostat_climate.cpp | 14 +- esphome/components/time/automation.cpp | 1 + esphome/components/time/real_time_clock.cpp | 2 +- .../time_based/time_based_cover.cpp | 1 + .../components/tlc59208f/tlc59208f_output.cpp | 2 +- .../components/tlc59208f/tlc59208f_output.h | 1 + esphome/components/tlc5947/tlc5947.h | 3 +- esphome/components/tm1637/tm1637.cpp | 29 +- esphome/components/tm1637/tm1637.h | 2 +- esphome/components/tm1651/__init__.py | 15 +- esphome/components/tm1651/tm1651.cpp | 4 + esphome/components/tm1651/tm1651.h | 14 +- esphome/components/tmp102/tmp102.cpp | 9 +- .../components/tof10120/tof10120_sensor.cpp | 8 +- esphome/components/toshiba/toshiba.cpp | 2 +- .../total_daily_energy/total_daily_energy.cpp | 4 +- .../total_daily_energy/total_daily_energy.h | 1 + esphome/components/tsl2591/tsl2591.cpp | 3 +- esphome/components/ttp229_bsf/ttp229_bsf.h | 2 +- esphome/components/ttp229_lsf/ttp229_lsf.cpp | 6 +- .../components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/tuya/tuya.cpp | 5 +- esphome/components/tx20/sensor.py | 4 +- esphome/components/tx20/tx20.cpp | 8 +- esphome/components/tx20/tx20.h | 8 +- esphome/components/uart/__init__.py | 45 +- esphome/components/uart/uart.cpp | 50 +- esphome/components/uart/uart.h | 147 +-- esphome/components/uart/uart_component.cpp | 24 + esphome/components/uart/uart_component.h | 67 ++ ...2.cpp => uart_component_esp32_arduino.cpp} | 103 +- .../uart/uart_component_esp32_arduino.h | 40 + ...esp8266.cpp => uart_component_esp8266.cpp} | 163 ++-- .../components/uart/uart_component_esp8266.h | 79 ++ .../uart/uart_component_esp_idf.cpp | 201 ++++ .../components/uart/uart_component_esp_idf.h | 39 + esphome/components/uln2003/uln2003.h | 2 +- .../ultrasonic/ultrasonic_sensor.cpp | 20 +- .../components/ultrasonic/ultrasonic_sensor.h | 7 +- esphome/components/uptime/uptime_sensor.cpp | 1 + esphome/components/vl53l0x/vl53l0x_sensor.cpp | 6 +- esphome/components/vl53l0x/vl53l0x_sensor.h | 2 +- esphome/components/web_server/web_server.cpp | 7 +- esphome/components/web_server/web_server.h | 4 + .../web_server_base/web_server_base.cpp | 12 +- .../web_server_base/web_server_base.h | 4 + esphome/components/wifi/__init__.py | 23 +- esphome/components/wifi/wifi_component.cpp | 81 +- esphome/components/wifi/wifi_component.h | 57 +- ...2.cpp => wifi_component_esp32_arduino.cpp} | 80 +- .../wifi/wifi_component_esp8266.cpp | 69 +- .../wifi/wifi_component_esp_idf.cpp | 901 ++++++++++++++++++ .../wifi_info/wifi_info_text_sensor.h | 18 +- .../wifi_signal/wifi_signal_sensor.h | 2 +- esphome/components/wled/__init__.py | 2 +- esphome/components/wled/wled_light_effect.cpp | 8 +- esphome/components/wled/wled_light_effect.h | 4 + esphome/components/xiaomi_ble/xiaomi_ble.cpp | 2 +- esphome/components/xiaomi_ble/xiaomi_ble.h | 2 +- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 2 +- esphome/components/xiaomi_cgd1/xiaomi_cgd1.h | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.h | 2 +- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 2 +- esphome/components/xiaomi_cgg1/xiaomi_cgg1.h | 2 +- .../components/xiaomi_cgpr1/xiaomi_cgpr1.cpp | 2 +- .../components/xiaomi_cgpr1/xiaomi_cgpr1.h | 2 +- .../xiaomi_gcls002/xiaomi_gcls002.cpp | 2 +- .../xiaomi_gcls002/xiaomi_gcls002.h | 2 +- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp | 2 +- .../xiaomi_hhccjcy01/xiaomi_hhccjcy01.h | 2 +- .../xiaomi_hhccpot002/xiaomi_hhccpot002.cpp | 2 +- .../xiaomi_hhccpot002/xiaomi_hhccpot002.h | 2 +- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp | 2 +- .../xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h | 2 +- .../xiaomi_lywsd02/xiaomi_lywsd02.cpp | 2 +- .../xiaomi_lywsd02/xiaomi_lywsd02.h | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h | 2 +- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp | 2 +- .../xiaomi_lywsdcgq/xiaomi_lywsdcgq.h | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.h | 2 +- .../xiaomi_miscale/xiaomi_miscale.cpp | 2 +- .../xiaomi_miscale/xiaomi_miscale.h | 2 +- .../xiaomi_miscale2/xiaomi_miscale2.cpp | 2 +- .../xiaomi_miscale2/xiaomi_miscale2.h | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp | 2 +- .../xiaomi_mjyd02yla/xiaomi_mjyd02yla.h | 2 +- .../xiaomi_mue4094rt/xiaomi_mue4094rt.cpp | 2 +- .../xiaomi_mue4094rt/xiaomi_mue4094rt.h | 2 +- .../xiaomi_wx08zm/xiaomi_wx08zm.cpp | 2 +- .../components/xiaomi_wx08zm/xiaomi_wx08zm.h | 2 +- esphome/components/zyaura/sensor.py | 8 +- esphome/components/zyaura/zyaura.cpp | 14 +- esphome/components/zyaura/zyaura.h | 16 +- esphome/config.py | 724 ++++++++------ esphome/config_validation.py | 127 ++- esphome/const.py | 32 +- esphome/core/__init__.py | 82 +- esphome/core/application.cpp | 21 +- esphome/core/application.h | 1 - esphome/core/application_esp32.cpp | 21 - esphome/core/application_esp8266.cpp | 14 - esphome/core/component.cpp | 4 +- esphome/core/component.h | 2 +- esphome/core/config.py | 229 ++--- esphome/core/defines.h | 43 +- esphome/core/esphal.cpp | 303 ------ esphome/core/esphal.h | 128 --- esphome/core/gpio.h | 98 ++ esphome/core/hal.h | 47 + esphome/core/helpers.cpp | 52 +- esphome/core/helpers.h | 12 +- esphome/core/log.cpp | 2 +- esphome/core/log.h | 12 +- esphome/core/macros.h | 4 +- esphome/core/preferences.cpp | 303 ------ esphome/core/preferences.h | 127 +-- esphome/core/scheduler.cpp | 3 +- esphome/core/util.cpp | 105 -- esphome/core/util.h | 17 - esphome/cpp_generator.py | 4 + esphome/cpp_helpers.py | 15 +- esphome/cpp_types.py | 4 +- esphome/dashboard/dashboard.py | 10 +- esphome/final_validate.py | 24 - esphome/helpers.py | 12 +- esphome/loader.py | 47 +- esphome/pins.py | 443 +++------ esphome/platformio_api.py | 4 +- esphome/storage_json.py | 31 +- esphome/wizard.py | 16 +- esphome/writer.py | 114 +-- platformio.ini | 77 +- requirements.txt | 4 + script/ci-custom.py | 27 +- script/clang-tidy | 9 +- script/helpers.py | 25 +- .../binary_sensor/test_binary_sensor.py | 2 +- tests/dummy_main.cpp | 6 - tests/test1.yaml | 72 +- tests/test2.yaml | 6 +- tests/test3.yaml | 2 +- tests/test4.yaml | 19 +- tests/test5.yaml | 24 +- tests/unit_tests/test_core.py | 4 +- tests/unit_tests/test_cpp_helpers.py | 10 - tests/unit_tests/test_pins.py | 346 ------- tests/unit_tests/test_wizard.py | 2 +- 583 files changed, 9008 insertions(+), 5420 deletions(-) create mode 100644 esphome/components/esp32/__init__.py rename esphome/{ => components/esp32}/boards.py (77%) create mode 100644 esphome/components/esp32/const.py create mode 100644 esphome/components/esp32/core.cpp create mode 100644 esphome/components/esp32/gpio.py create mode 100644 esphome/components/esp32/gpio_arduino.cpp create mode 100644 esphome/components/esp32/gpio_arduino.h create mode 100644 esphome/components/esp32/gpio_idf.cpp create mode 100644 esphome/components/esp32/gpio_idf.h create mode 100644 esphome/components/esp32/preferences.cpp create mode 100644 esphome/components/esp32/preferences.h create mode 100644 esphome/components/esp8266/__init__.py create mode 100644 esphome/components/esp8266/boards.py create mode 100644 esphome/components/esp8266/const.py create mode 100644 esphome/components/esp8266/core.cpp create mode 100644 esphome/components/esp8266/gpio.cpp create mode 100644 esphome/components/esp8266/gpio.h create mode 100644 esphome/components/esp8266/gpio.py create mode 100644 esphome/components/esp8266/preferences.cpp create mode 100644 esphome/components/esp8266/preferences.h create mode 100644 esphome/components/i2c/i2c_bus.h create mode 100644 esphome/components/i2c/i2c_bus_arduino.cpp create mode 100644 esphome/components/i2c/i2c_bus_arduino.h create mode 100644 esphome/components/i2c/i2c_bus_esp_idf.cpp create mode 100644 esphome/components/i2c/i2c_bus_esp_idf.h create mode 100644 esphome/components/mdns/__init__.py create mode 100644 esphome/components/mdns/mdns_component.cpp create mode 100644 esphome/components/mdns/mdns_component.h create mode 100644 esphome/components/mdns/mdns_esp32_arduino.cpp create mode 100644 esphome/components/mdns/mdns_esp8266.cpp create mode 100644 esphome/components/mdns/mdns_esp_idf.cpp create mode 100644 esphome/components/network/ip_address.h create mode 100644 esphome/components/network/util.cpp create mode 100644 esphome/components/network/util.h create mode 100644 esphome/components/uart/uart_component.cpp create mode 100644 esphome/components/uart/uart_component.h rename esphome/components/uart/{uart_esp32.cpp => uart_component_esp32_arduino.cpp} (64%) create mode 100644 esphome/components/uart/uart_component_esp32_arduino.h rename esphome/components/uart/{uart_esp8266.cpp => uart_component_esp8266.cpp} (60%) create mode 100644 esphome/components/uart/uart_component_esp8266.h create mode 100644 esphome/components/uart/uart_component_esp_idf.cpp create mode 100644 esphome/components/uart/uart_component_esp_idf.h rename esphome/components/wifi/{wifi_component_esp32.cpp => wifi_component_esp32_arduino.cpp} (89%) create mode 100644 esphome/components/wifi/wifi_component_esp_idf.cpp delete mode 100644 esphome/core/application_esp32.cpp delete mode 100644 esphome/core/application_esp8266.cpp delete mode 100644 esphome/core/esphal.cpp delete mode 100644 esphome/core/esphal.h create mode 100644 esphome/core/gpio.h create mode 100644 esphome/core/hal.h delete mode 100644 esphome/core/preferences.cpp delete mode 100644 tests/unit_tests/test_pins.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 875db0f9fd..c2c434a9b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: name: Run script/clang-format - id: clang-tidy name: Run script/clang-tidy for ESP8266 - options: --environment esp8266-tidy --grep ARDUINO_ARCH_ESP8266 + options: --environment esp8266-tidy --grep USE_ESP8266 pio_cache_key: tidyesp8266 - id: clang-tidy name: Run script/clang-tidy for ESP32 1/4 @@ -64,6 +64,10 @@ jobs: name: Run script/clang-tidy for ESP32 4/4 options: --environment esp32-tidy --split-num 4 --split-at 4 pio_cache_key: tidyesp32 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 esp-idf + options: --environment esp32-idf-tidy --grep USE_ESP_IDF + pio_cache_key: tidyesp32-idf steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index cd7822bded..c52909f7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,5 @@ tests/.esphome/ /.temp-clang-tidy.cpp /.temp/ .pio/ + +sdkconfig.* diff --git a/CODEOWNERS b/CODEOWNERS index ab3a0815ce..385ec4d892 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -44,9 +44,11 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz +esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/fastled_base/* @OttoWinter @@ -81,6 +83,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core diff --git a/esphome/__main__.py b/esphome/__main__.py index ba1019869d..6bc7e065ce 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -245,7 +245,7 @@ def upload_program(config, args, host): ota_conf = config[CONF_OTA] remote_port = ota_conf[CONF_PORT] - password = ota_conf[CONF_PASSWORD] + password = ota_conf.get(CONF_PASSWORD, "") return espota2.run_ota(host, remote_port, password, CORE.firmware_bin) diff --git a/esphome/codegen.py b/esphome/codegen.py index c05cc5efca..1b38fd9ed2 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -30,6 +30,7 @@ from esphome.cpp_generator import ( # noqa add_library, add_build_flag, add_define, + add_platformio_option, get_variable, get_variable_with_full_id, process_lambda, @@ -78,4 +79,6 @@ from esphome.cpp_types import ( # noqa JsonObjectConstRef, Controller, GPIOPin, + InternalGPIOPin, + gpio_Flags, ) diff --git a/esphome/components/a4988/a4988.h b/esphome/components/a4988/a4988.h index 5be0f3ce69..0fe7891110 100644 --- a/esphome/components/a4988/a4988.h +++ b/esphome/components/a4988/a4988.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/stepper/stepper.h" namespace esphome { diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e86703bfe1..a7f1e6f3a9 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -1,10 +1,16 @@ +#ifdef USE_ARDUINO + #include "ac_dimmer.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include +#endif namespace esphome { namespace ac_dimmer { @@ -25,7 +31,7 @@ static const uint32_t GATE_ENABLE_TIME = 50; /// Function called from timer interrupt /// Input is current time in microseconds (micros()) /// Returns when next "event" is expected in µs, or 0 if no such event known. -uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { +uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { // If no ZC signal received yet. if (this->crossed_zero_at == 0) return 0; @@ -37,13 +43,13 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) { this->enable_time_us = 0; - this->gate_pin->digital_write(true); + this->gate_pin.digital_write(true); // Prevent too short pulses - this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME); + this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME); } if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) { this->disable_time_us = 0; - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); } if (time_since_zc < this->enable_time_us) @@ -63,7 +69,7 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { } /// Run timer interrupt code and return in how many µs the next event is expected -uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() { +uint32_t IRAM_ATTR HOT timer_interrupt() { // run at least with 1kHz uint32_t min_dt_us = 1000; uint32_t now = micros(); @@ -80,7 +86,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() { } /// GPIO interrupt routine, called when ZC pin triggers -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { +void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { uint32_t prev_crossed = this->crossed_zero_at; // 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms @@ -97,7 +103,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { if (this->value == 65535) { // fully on, enable output immediately - this->gate_pin->digital_write(true); + this->gate_pin.digital_write(true); } else if (this->init_cycle) { // send a full cycle this->init_cycle = false; @@ -105,29 +111,29 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { this->disable_time_us = cycle_time_us; } else if (this->value == 0) { // fully off, disable output immediately - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); } else { if (this->method == DIM_METHOD_TRAILING) { this->enable_time_us = 1; // cannot be 0 - this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535); + this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535); } else { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% - this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10); + this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10); } else { - this->gate_pin->digital_write(false); + this->gate_pin.digital_write(false); this->disable_time_us = this->cycle_time_us; } } } } -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { +void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { // Attaching pin interrupts on the same pin will override the previous interrupt // However, the user expects that multiple dimmers sharing the same ZC pin will work. // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers @@ -141,11 +147,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // ESP32 implementation, uses basically the same code but needs to wrap // timer_interrupt() function to auto-reschedule static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } +void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } #endif void AcDimmer::setup() { @@ -174,15 +180,16 @@ void AcDimmer::setup() { if (setup_zero_cross_pin) { this->zero_cross_pin_->setup(); this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr(); - this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING); + this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, + gpio::INTERRUPT_FALLING_EDGE); } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // Uses ESP8266 waveform (soft PWM) class // PWM and AcDimmer can even run at the same time this way setTimer1Callback(&timer_interrupt); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // 80 Divider -> 1 count=1µs dimmer_timer = timerBegin(0, 80, true); timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true); @@ -218,3 +225,5 @@ void AcDimmer::dump_config() { } // namespace ac_dimmer } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ac_dimmer/ac_dimmer.h b/esphome/components/ac_dimmer/ac_dimmer.h index 00da061cfd..fd1bbc28db 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.h +++ b/esphome/components/ac_dimmer/ac_dimmer.h @@ -1,7 +1,9 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" namespace esphome { @@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR struct AcDimmerDataStore { /// Zero-cross pin - ISRInternalGPIOPin *zero_cross_pin; + ISRInternalGPIOPin zero_cross_pin; /// Zero-cross pin number - used to share ZC pin across multiple dimmers uint8_t zero_cross_pin_number; /// Output pin to write to - ISRInternalGPIOPin *gate_pin; + ISRInternalGPIOPin gate_pin; /// Value of the dimmer - 0 to 65535. uint16_t value; /// Minimum power for activation @@ -37,7 +39,7 @@ struct AcDimmerDataStore { void gpio_intr(); static void s_gpio_intr(AcDimmerDataStore *store); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 static void s_timer_intr(); #endif }; @@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component { void setup() override; void dump_config() override; - void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; } - void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; } + void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; } + void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; } void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; } void set_method(DimMethod method) { method_ = method; } protected: void write_state(float state) override; - GPIOPin *gate_pin_; - GPIOPin *zero_cross_pin_; + InternalGPIOPin *gate_pin_; + InternalGPIOPin *zero_cross_pin_; AcDimmerDataStore store_; bool init_with_half_cycle_; DimMethod method_; @@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component { } // namespace ac_dimmer } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 2c37d325eb..c39fc382b6 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -19,17 +19,20 @@ DIM_METHODS = { CONF_GATE_PIN = "gate_pin" CONF_ZERO_CROSS_PIN = "zero_cross_pin" CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( - { - cv.Required(CONF_ID): cv.declare_id(AcDimmer), - cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, - cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( - DIM_METHODS, upper=True, space="_" - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(AcDimmer), + cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, + cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( + DIM_METHODS, upper=True, space="_" + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 4106a0c49b..9a05c1d66b 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #ifdef USE_ADC_SENSOR_VCC +#include ADC_MODE(ADC_VCC) #endif @@ -10,7 +11,7 @@ namespace adc { static const char *const TAG = "adc"; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } inline adc1_channel_t gpio_to_adc1(uint8_t pin) { @@ -57,28 +58,28 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); #ifndef USE_ADC_SENSOR_VCC - GPIOPin(this->pin_, INPUT).setup(); + pin_->setup(); #endif -#ifdef ARDUINO_ARCH_ESP32 - adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); +#ifdef USE_ESP32 + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 - adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); #endif #endif } void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC ESP_LOGCONFIG(TAG, " Pin: VCC"); #else - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + LOG_PIN(" Pin: ", pin_); #endif #endif -#ifdef ARDUINO_ARCH_ESP32 - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); +#ifdef USE_ESP32 + LOG_PIN(" Pin: ", pin_); switch (this->attenuation_) { case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); @@ -105,8 +106,8 @@ void ADCSensor::update() { this->publish_state(value_v); } float ADCSensor::sample() { -#ifdef ARDUINO_ARCH_ESP32 - int raw = adc1_get_raw(gpio_to_adc1(pin_)); +#ifdef USE_ESP32 + int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); float value_v = raw / 4095.0f; #if CONFIG_IDF_TARGET_ESP32 switch (this->attenuation_) { @@ -146,15 +147,15 @@ float ADCSensor::sample() { return value_v; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT #endif #endif } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 4591ed758d..b8c702be4e 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -1,12 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/defines.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "driver/adc.h" #endif @@ -15,7 +15,7 @@ namespace adc { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { public: -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); #endif @@ -27,17 +27,17 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage void dump_config() override; /// `HARDWARE_LATE` setup priority. float get_setup_priority() const override; - void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } float sample() override; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::string unique_id() override; #endif protected: - uint8_t pin_; + InternalGPIOPin *pin_; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 7c32e4a923..9a0407d0f4 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -5,11 +5,13 @@ from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_ATTENUATION, CONF_ID, + CONF_INPUT, CONF_PIN, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.core import CORE AUTO_LOAD = ["voltage_sampler"] @@ -23,10 +25,34 @@ ATTENUATION_MODES = { def validate_adc_pin(value): - vcc = str(value).upper() - if vcc == "VCC": - return cv.only_on_esp8266(vcc) - return pins.analog_pin(value) + if str(value).upper() == "VCC": + return cv.only_on_esp8266("VCC") + + if CORE.is_esp32: + from esphome.components.esp32 import is_esp32c3 + + value = pins.internal_gpio_input_pin_number(value) + if is_esp32c3(): + if not (0 <= value <= 4): # ADC1 + raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") + if not (32 <= value <= 39): # ADC1 + raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") + elif CORE.is_esp8266: + from esphome.components.esp8266.gpio import CONF_ANALOG + + value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( + value + ) + + if value != 17: # A0 + raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") + return pins.gpio_pin_schema( + {CONF_ANALOG: True, CONF_INPUT: True}, internal=True + )(value) + else: + raise NotImplementedError + + return pins.internal_gpio_input_pin_schema(value) adc_ns = cg.esphome_ns.namespace("adc") @@ -62,7 +88,8 @@ async def to_code(config): if config[CONF_PIN] == "VCC": cg.add_define("USE_ADC_SENSOR_VCC") else: - cg.add(var.set_pin(config[CONF_PIN])) + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: cg.add(var.set_attenuation(config[CONF_ATTENUATION])) diff --git a/esphome/components/ade7953/ade7953.cpp b/esphome/components/ade7953/ade7953.cpp index 0e6d5624c1..2c61fc6a44 100644 --- a/esphome/components/ade7953/ade7953.cpp +++ b/esphome/components/ade7953/ade7953.cpp @@ -8,9 +8,7 @@ static const char *const TAG = "ade7953"; void ADE7953::dump_config() { ESP_LOGCONFIG(TAG, "ADE7953:"); - if (this->has_irq_) { - ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_); - } + LOG_PIN(" IRQ Pin: ", irq_pin_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_); @@ -20,27 +18,28 @@ void ADE7953::dump_config() { LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_); } -#define ADE_PUBLISH_(name, factor) \ - if ((name) && this->name##_sensor_) { \ - float value = *(name) / (factor); \ +#define ADE_PUBLISH_(name, val, factor) \ + if (err == i2c::ERROR_OK && this->name##_sensor_) { \ + float value = (val) / (factor); \ this->name##_sensor_->publish_state(value); \ } -#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor) +#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor) void ADE7953::update() { if (!this->is_setup_) return; - auto active_power_a = this->ade_read_(0x0312); - ADE_PUBLISH(active_power_a, 154.0f); - auto active_power_b = this->ade_read_(0x0313); - ADE_PUBLISH(active_power_b, 154.0f); - auto current_a = this->ade_read_(0x031A); - ADE_PUBLISH(current_a, 100000.0f); - auto current_b = this->ade_read_(0x031B); - ADE_PUBLISH(current_b, 100000.0f); - auto voltage = this->ade_read_(0x031C); - ADE_PUBLISH(voltage, 26000.0f); + uint32_t val; + i2c::ErrorCode err = ade_read_32_(0x0312, &val); + ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f); + err = ade_read_32_(0x0313, &val); + ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f); + err = ade_read_32_(0x031A, &val); + ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f); + err = ade_read_32_(0x031B, &val); + ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f); + err = ade_read_32_(0x031C, &val); + ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f); // auto apparent_power_a = this->ade_read_(0x0310); // auto apparent_power_b = this->ade_read_(0x0311); diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index 5a582a991a..c6fb383ed8 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/sensor/sensor.h" @@ -10,10 +10,7 @@ namespace ade7953 { class ADE7953 : public i2c::I2CDevice, public PollingComponent { public: - void set_irq_pin(uint8_t irq_pin) { - has_irq_ = true; - irq_pin_number_ = irq_pin; - } + void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; } void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; } @@ -25,15 +22,13 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { } void setup() override { - if (this->has_irq_) { - auto pin = GPIOPin(this->irq_pin_number_, INPUT); - this->irq_pin_ = &pin; + if (this->irq_pin_ != nullptr) { this->irq_pin_->setup(); } this->set_timeout(100, [this]() { - this->ade_write_(0x0010, 0x04); - this->ade_write_(0x00FE, 0xAD); - this->ade_write_(0x0120, 0x0030); + this->ade_write_8_(0x0010, 0x04); + this->ade_write_8_(0x00FE, 0xAD); + this->ade_write_16_(0x0120, 0x0030); this->is_setup_ = true; }); } @@ -43,31 +38,51 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { void update() override; protected: - template bool ade_write_(uint16_t reg, T value) { + i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) { std::vector data; data.push_back(reg >> 8); data.push_back(reg >> 0); - for (int i = sizeof(T) - 1; i >= 0; i--) - data.push_back(value >> (i * 8)); - return this->write_bytes_raw(data); + data.push_back(value); + return write(data.data(), data.size()); } - template optional ade_read_(uint16_t reg) { - uint8_t hi = reg >> 8; - uint8_t lo = reg >> 0; - if (!this->write_bytes_raw({hi, lo})) - return {}; - auto ret = this->read_bytes_raw(); - if (!ret.has_value()) - return {}; - T result = 0; - for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--) - result |= T((*ret)[i]) << (j * 8); - return result; + i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) { + std::vector data; + data.push_back(reg >> 8); + data.push_back(reg >> 0); + data.push_back(value >> 8); + data.push_back(value >> 0); + return write(data.data(), data.size()); + } + i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) { + std::vector data; + data.push_back(reg >> 8); + data.push_back(reg >> 0); + data.push_back(value >> 24); + data.push_back(value >> 16); + data.push_back(value >> 8); + data.push_back(value >> 0); + return write(data.data(), data.size()); + } + i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) { + uint8_t reg_data[2]; + reg_data[0] = reg >> 8; + reg_data[1] = reg >> 0; + i2c::ErrorCode err = write(reg_data, 2); + if (err != i2c::ERROR_OK) + return err; + uint8_t recv[4]; + err = read(recv, 4); + if (err != i2c::ERROR_OK) + return err; + *value = 0; + *value |= ((uint32_t) recv[0]) << 24; + *value |= ((uint32_t) recv[1]) << 24; + *value |= ((uint32_t) recv[2]) << 24; + *value |= ((uint32_t) recv[3]) << 24; + return i2c::ERROR_OK; } - bool has_irq_ = false; - uint8_t irq_pin_number_; - GPIOPin *irq_pin_{nullptr}; + InternalGPIOPin *irq_pin_ = nullptr; bool is_setup_{false}; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_a_sensor_{nullptr}; diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py index 80dafe2417..d02f466091 100644 --- a/esphome/components/ade7953/sensor.py +++ b/esphome/components/ade7953/sensor.py @@ -29,7 +29,7 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(ADE7953), - cv.Optional(CONF_IRQ_PIN): pins.input_pin, + cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, @@ -73,7 +73,8 @@ async def to_code(config): await i2c.register_i2c_device(var, config) if CONF_IRQ_PIN in config: - cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) + irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) + cg.add(var.set_irq_pin(irq_pin)) for key in [ CONF_VOLTAGE, diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 92f0ffbe3a..beb379db93 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -1,5 +1,6 @@ #include "ads1115.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ads1115 { @@ -159,7 +160,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); } void ADS1115Sensor::update() { float v = this->parent_->request_measurement(this); - if (!isnan(v)) { + if (!std::isnan(v)) { ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); this->publish_state(v); } diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index da8c6e844e..713199212c 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -14,6 +14,7 @@ #include "aht10.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace aht10 { @@ -33,8 +34,19 @@ void AHT10Component::setup() { this->mark_failed(); return; } - uint8_t data; - if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) { + uint8_t data = 0; + if (this->write(&data, 1) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed!"); + this->mark_failed(); + return; + } + delay(AHT10_DEFAULT_DELAY); + if (this->read(&data, 1) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed!"); + this->mark_failed(); + return; + } + if (this->read(&data, 1) != i2c::ERROR_OK) { ESP_LOGD(TAG, "Communication with AHT10 failed!"); this->mark_failed(); return; @@ -55,15 +67,26 @@ void AHT10Component::update() { return; } uint8_t data[6]; - uint8_t delay = AHT10_DEFAULT_DELAY; + uint8_t delay_ms = AHT10_DEFAULT_DELAY; if (this->humidity_sensor_ != nullptr) - delay = AHT10_HUMIDITY_DELAY; + delay_ms = AHT10_HUMIDITY_DELAY; + bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { - ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis()); + ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); delay_microseconds_accurate(4); - if (!this->read_bytes(0, data, 6, delay)) { + + uint8_t reg = 0; + if (this->write(®, 1) != i2c::ERROR_OK) { ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); - } else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy + continue; + } + delay(delay_ms); + if (this->read(data, 6) != i2c::ERROR_OK) { + ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); + continue; + } + + if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy ESP_LOGD(TAG, "AHT10 is busy, waiting..."); } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { // Unrealistic humidity (0x0) @@ -80,11 +103,12 @@ void AHT10Component::update() { } } else { // data is valid, we can break the loop - ESP_LOGVV(TAG, "Answer at %6ld", millis()); + ESP_LOGVV(TAG, "Answer at %6u", millis()); + success = true; break; } } - if ((data[0] & 0x80) == 0x80) { + if (!success || (data[0] & 0x80) == 0x80) { ESP_LOGE(TAG, "Measurements reading timed-out!"); this->status_set_warning(); return; @@ -105,7 +129,7 @@ void AHT10Component::update() { this->temperature_sensor_->publish_state(temperature); } if (this->humidity_sensor_ != nullptr) { - if (isnan(humidity)) + if (std::isnan(humidity)) ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum"); this->humidity_sensor_->publish_state(humidity); } diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index a843bcb145..951961cb1b 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -1,7 +1,7 @@ #include "airthings_listener.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace airthings_ble { diff --git a/esphome/components/airthings_ble/airthings_listener.h b/esphome/components/airthings_ble/airthings_listener.h index cd240ac1ba..52f69ea970 100644 --- a/esphome/components/airthings_ble/airthings_listener.h +++ b/esphome/components/airthings_ble/airthings_listener.h @@ -1,10 +1,9 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include namespace esphome { namespace airthings_ble { diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 6b4780726d..a8153ae87a 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -1,6 +1,6 @@ #include "airthings_wave_plus.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO namespace esphome { namespace airthings_wave_plus { @@ -141,4 +141,4 @@ void AirthingsWavePlus::setup() {} } // namespace airthings_wave_plus } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 88d7a7a01b..5677f05a62 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -1,6 +1,6 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include @@ -72,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientN } // namespace airthings_wave_plus } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 4109fca700..8b902ea81c 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -34,7 +34,7 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_( ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(AirthingsWavePlus), @@ -83,7 +83,9 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("5mins")) - .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(ble_client.BLE_CLIENT_SCHEMA), + # Until BLEUUID reference removed + cv.only_with_arduino, ) diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index 7e8795dd30..b53eb69464 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -5,6 +5,7 @@ #include "am2320.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace am2320 { @@ -77,7 +78,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len if (conversion > 0) delay(conversion); - return this->parent_->raw_receive(this->address_, data, len); + return this->read(data, len) == i2c::ERROR_OK; } bool AM2320Component::read_data_(uint8_t *data) { diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index 94be194a1d..2130b334be 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -1,7 +1,8 @@ #include "am43.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace am43 { diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h index 025d075b68..8dfe83e3a3 100644 --- a/esphome/components/am43/am43.h +++ b/esphome/components/am43/am43.h @@ -6,7 +6,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/am43/am43_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 1067ce9ebb..fd337ba17b 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -1,7 +1,7 @@ #include "am43_cover.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace am43 { diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h index dba1391b63..f33f2d1734 100644 --- a/esphome/components/am43/cover/am43_cover.h +++ b/esphome/components/am43/cover/am43_cover.h @@ -6,7 +6,7 @@ #include "esphome/components/cover/cover.h" #include "esphome/components/am43/am43_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index d0ea818669..8e0724ad13 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -1,7 +1,7 @@ #include "anova.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace anova { diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index e033513013..554024e389 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -6,7 +6,7 @@ #include "esphome/components/climate/climate.h" #include "anova_base.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 15b0cb39f8..9ee873ac64 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -1,5 +1,6 @@ #include "apds9960.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace apds9960 { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0fa4ca6397..450375f7cd 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1,7 +1,8 @@ #include "api_connection.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/version.h" +#include "esphome/core/hal.h" #include #ifdef USE_DEEP_SLEEP @@ -48,7 +49,7 @@ void APIConnection::loop() { if (this->remove_) return; - if (!network_is_connected()) { + if (!network::is_connected()) { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 15014b7937..00f28457ae 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -3,6 +3,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" #include "proto.h" +#include namespace esphome { namespace api { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index d6b85d257c..580e147245 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2,6 +2,7 @@ // See scripts/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/log.h" +#include namespace esphome { namespace api { diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d84a35747c..8728597537 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -5,6 +5,8 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" #include "esphome/core/version.h" +#include "esphome/core/hal.h" +#include "esphome/components/network/util.h" #include #ifdef USE_LOGGER @@ -130,7 +132,7 @@ void APIServer::loop() { } void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index dd054bab7e..edbc916b01 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -246,6 +246,7 @@ class ProtoWriteBuffer { class ProtoMessage { public: + virtual ~ProtoMessage() = default; virtual void encode(ProtoWriteBuffer buffer) const = 0; void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/as3935/as3935.h b/esphome/components/as3935/as3935.h index 76c7697d38..2e65aab4d1 100644 --- a/esphome/components/as3935/as3935.h +++ b/esphome/components/as3935/as3935.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/as3935_i2c/as3935_i2c.cpp b/esphome/components/as3935_i2c/as3935_i2c.cpp index 1a1cd8fe82..3a7fa7bf84 100644 --- a/esphome/components/as3935_i2c/as3935_i2c.cpp +++ b/esphome/components/as3935_i2c/as3935_i2c.cpp @@ -25,8 +25,12 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t I2CAS3935Component::read_register(uint8_t reg) { uint8_t value; - if (!this->read_byte(reg, &value, 2)) { - ESP_LOGW(TAG, "Read failed!"); + if (write(®, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Writing register failed!"); + return 0; + } + if (read(&value, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Reading register failed!"); return 0; } return value; diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 22d4bb1fcb..8789448792 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,9 +1,15 @@ # Dummy integration to allow relying on AsyncTCP import esphome.codegen as cg +import esphome.config_validation as cv from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@OttoWinter"] +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +) + @coroutine_with_priority(200.0) async def to_code(config): diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 1dba259143..b04d634103 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -1,7 +1,7 @@ #include "atc_mithermometer.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace atc_mithermometer { diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index eff14811be..291c1d96cd 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace atc_mithermometer { diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 3098d462d2..bc1463fd1c 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -1,7 +1,7 @@ #include "b_parasite.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace b_parasite { @@ -79,4 +79,4 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { } // namespace b_parasite } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32 diff --git a/esphome/components/b_parasite/b_parasite.h b/esphome/components/b_parasite/b_parasite.h index 04f648ab63..bdd9a01b83 100644 --- a/esphome/components/b_parasite/b_parasite.h +++ b/esphome/components/b_parasite/b_parasite.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace b_parasite { @@ -37,4 +37,4 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene } // namespace b_parasite } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32 diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 4b41684707..5645f46f1c 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -69,7 +69,8 @@ void BangBangClimate::compute_state_() { this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; } - if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature_low) || + std::isnan(this->target_temperature_high)) { // if any control parameters are nan, go to OFF action (not IDLE!) this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 43af116c9e..3645a45bf9 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -71,10 +71,11 @@ void BH1750Sensor::update() { float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } void BH1750Sensor::read_data_() { uint16_t raw_value; - if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) { + if (!this->read(reinterpret_cast(&raw_value), 2)) { this->status_set_warning(); return; } + raw_value = i2c::i2ctohs(raw_value); float lx = float(raw_value) / 1.2f; lx *= 69.0f / this->measurement_duration_; diff --git a/esphome/components/binary_sensor/automation.h b/esphome/components/binary_sensor/automation.h index 0c1e80afba..31bf1a5565 100644 --- a/esphome/components/binary_sensor/automation.h +++ b/esphome/components/binary_sensor/automation.h @@ -4,6 +4,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 2db609de55..d015d4019c 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 0398113f83..f584cdf79e 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "ble_client.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index b9d16ddacd..a69460e8b6 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/ble_client/sensor/automation.h b/esphome/components/ble_client/sensor/automation.h index a528493947..2255a5ac55 100644 --- a/esphome/components/ble_client/sensor/automation.h +++ b/esphome/components/ble_client/sensor/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/sensor/ble_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 270822be9d..4459163389 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/sensor/ble_sensor.h b/esphome/components/ble_client/sensor/ble_sensor.h index 25e996b6ee..52c9e9d5ca 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.h +++ b/esphome/components/ble_client/sensor/ble_sensor.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 669984d705..00593da9d6 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_client { diff --git a/esphome/components/ble_client/switch/ble_switch.h b/esphome/components/ble_client/switch/ble_switch.h index f91af533f1..2e19c8aeef 100644 --- a/esphome/components/ble_client/switch/ble_switch.h +++ b/esphome/components/ble_client/switch/ble_switch.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/switch/switch.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/ble_presence/ble_presence_device.cpp b/esphome/components/ble_presence/ble_presence_device.cpp index 1355c2bcc3..e482bb9a78 100644 --- a/esphome/components/ble_presence/ble_presence_device.cpp +++ b/esphome/components/ble_presence/ble_presence_device.cpp @@ -1,7 +1,7 @@ #include "ble_presence_device.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_presence { diff --git a/esphome/components/ble_presence/ble_presence_device.h b/esphome/components/ble_presence/ble_presence_device.h index dfc36d68cb..40cda89e62 100644 --- a/esphome/components/ble_presence/ble_presence_device.h +++ b/esphome/components/ble_presence/ble_presence_device.h @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/binary_sensor/binary_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_presence { diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.cpp b/esphome/components/ble_rssi/ble_rssi_sensor.cpp index 096259d8d1..4b37fcc6ef 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.cpp +++ b/esphome/components/ble_rssi/ble_rssi_sensor.cpp @@ -1,7 +1,7 @@ #include "ble_rssi_sensor.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_rssi { diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.h b/esphome/components/ble_rssi/ble_rssi_sensor.h index 2082a52469..c6acae2593 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.h +++ b/esphome/components/ble_rssi/ble_rssi_sensor.h @@ -4,7 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_rssi { diff --git a/esphome/components/ble_scanner/ble_scanner.cpp b/esphome/components/ble_scanner/ble_scanner.cpp index 798824bb4e..f2cda227bb 100644 --- a/esphome/components/ble_scanner/ble_scanner.cpp +++ b/esphome/components/ble_scanner/ble_scanner.cpp @@ -1,7 +1,7 @@ #include "ble_scanner.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_scanner { diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 194494144c..542d5047ec 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -7,7 +7,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/text_sensor/text_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ble_scanner { diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index fe11b69945..18386430a2 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -186,7 +186,7 @@ void BME280Component::update() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { int32_t t_fine = 0; float temperature = this->read_temperature_(&t_fine); - if (isnan(temperature)) { + if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); this->status_set_warning(); return; diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index ee7db3c65f..99e0b6f860 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -1,5 +1,6 @@ #include "bme680.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace bme680 { diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index e2cb7491a6..0a8ca7f3c3 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -381,7 +381,7 @@ void BME680BSECComponent::delay_ms(uint32_t period) { void BME680BSECComponent::load_state_() { uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_)); - this->bsec_state_ = global_preferences.make_preference(hash, true); + this->bsec_state_ = global_preferences->make_preference(hash, true); uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; if (this->bsec_state_.load(&state)) { diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index d04a357ca2..b4348e8a74 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -139,7 +139,7 @@ void BMP280Component::update() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { int32_t t_fine = 0; float temperature = this->read_temperature_(&t_fine); - if (isnan(temperature)) { + if (std::isnan(temperature)) { ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values."); this->status_set_warning(); return; diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 102bfd370e..a1cc5734c1 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -12,14 +12,17 @@ CODEOWNERS = ["@OttoWinter"] captive_portal_ns = cg.esphome_ns.namespace("captive_portal") CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CaptivePortal), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( - web_server_base.WebServerBase - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CaptivePortal), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) @coroutine_with_priority(64.0) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 3341769956..9e00adae3d 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "captive_portal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -78,14 +80,14 @@ void CaptivePortal::start() { this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); - IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); - this->dns_server_->start(53, "*", ip); + network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); + this->dns_server_->start(53, "*", (uint32_t) ip); this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { bool not_found = false; if (!this->active_) { not_found = true; - } else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) { + } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { not_found = true; } @@ -94,8 +96,8 @@ void CaptivePortal::start() { return; } - auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString(); - req->redirect(url); + auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str(); + req->redirect(url.c_str()); }); this->initialized_ = true; @@ -151,3 +153,5 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo } // namespace captive_portal } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 399ffaabd3..b308de42b7 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include #include "esphome/core/component.h" @@ -73,3 +75,5 @@ extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid- } // namespace captive_portal } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 08df6f7774..6bdb4739cf 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -1,5 +1,6 @@ #include "ccs811.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ccs811 { @@ -124,12 +125,12 @@ void CCS811Component::send_env_data_() { float humidity = NAN; if (this->humidity_ != nullptr) humidity = this->humidity_->state; - if (isnan(humidity) || humidity < 0 || humidity > 100) + if (std::isnan(humidity) || humidity < 0 || humidity > 100) humidity = 50; float temperature = NAN; if (this->temperature_ != nullptr) temperature = this->temperature_->state; - if (isnan(temperature) || temperature < -25 || temperature > 50) + if (std::isnan(temperature) || temperature < -25 || temperature > 50) temperature = 25; // temperature has a 25° offset to allow negative temperatures temperature += 25; diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 383103b014..a365b933bb 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -95,7 +95,7 @@ void ClimateCall::validate_() { ESP_LOGW(TAG, " Cannot set target temperature for climate device " "with two-point target temperature!"); this->target_temperature_.reset(); - } else if (isnan(target)) { + } else if (std::isnan(target)) { ESP_LOGW(TAG, " Target temperature must not be NAN!"); this->target_temperature_.reset(); } @@ -107,11 +107,11 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->target_temperature_low_.has_value() && isnan(*this->target_temperature_low_)) { + if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) { ESP_LOGW(TAG, " Target temperature low must not be NAN!"); this->target_temperature_low_.reset(); } - if (this->target_temperature_high_.has_value() && isnan(*this->target_temperature_high_)) { + if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) { ESP_LOGW(TAG, " Target temperature low must not be NAN!"); this->target_temperature_high_.reset(); } @@ -318,17 +318,23 @@ void Climate::add_on_state_callback(std::function &&callback) { static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; optional Climate::restore_state_() { - this->rtc_ = - global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash() ^ + RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; return recovered; } void Climate::save_state_() { +#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY) +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif ClimateDeviceRestoreState state{}; // initialize as zero to prevent random data on stack triggering erase memset(&state, 0, sizeof(ClimateDeviceRestoreState)); +#if USE_ESP_IDF && !defined(CLANG_TIDY) +#pragma GCC diagnostic pop +#endif state.mode = this->mode; auto traits = this->get_traits(); diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 8d5ae6053d..b47d9b0141 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -52,7 +52,7 @@ void ClimateIR::setup() { this->swing_mode = climate::CLIMATE_SWING_OFF; } // Never send nan to HA - if (isnan(this->target_temperature)) + if (std::isnan(this->target_temperature)) this->target_temperature = 24; } diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 2b4452f5b7..e1ce211b64 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -180,7 +180,7 @@ void Cover::publish_state(bool save) { } } optional Cover::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); CoverRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 130cdfefba..0052b1426d 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -52,7 +52,7 @@ void CTClampSensor::loop() { // Perform a single sample float value = this->source_->sample(); - if (isnan(value)) + if (std::isnan(value)) return; this->sample_sum_ += value; diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.h b/esphome/components/ct_clamp/ct_clamp_sensor.h index 2f201c11a0..10601ab852 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.h +++ b/esphome/components/ct_clamp/ct_clamp_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 847d630e7c..0fc4108687 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -161,7 +161,7 @@ const std::string &DallasTemperatureSensor::get_address_name() { return this->address_name_; } -bool ICACHE_RAM_ATTR DallasTemperatureSensor::read_scratch_pad() { +bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { ESPOneWire *wire = this->parent_->one_wire_; if (!wire->reset()) { return false; diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 702d1eddc2..9278b83f7f 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -12,11 +12,11 @@ const int ONE_WIRE_ROM_SEARCH = 0xF0; ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} -bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { +bool HOT IRAM_ATTR ESPOneWire::reset() { uint8_t retries = 125; // Wait for communication to clear - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); do { if (--retries == 0) return false; @@ -24,12 +24,12 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { } while (!this->pin_->digital_read()); // Send 480µs LOW TX reset pulse - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); delayMicroseconds(480); // Switch into RX mode, letting the pin float - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // after 15µs-60µs wait time, responder pulls low for 60µs-240µs // let's have 70µs just in case delayMicroseconds(70); @@ -39,9 +39,9 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() { return r; } -void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) { +void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { // Initiate write/read by pulling low. - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); // bus sampled within 15µs and 60µs after pulling LOW. @@ -60,14 +60,14 @@ void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) { } } -bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() { +bool HOT IRAM_ATTR ESPOneWire::read_bit() { // Initiate read slot by pulling LOW for at least 1µs - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); delayMicroseconds(3); // release bus, we have to sample within 15µs of pulling low - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(10); bool r = this->pin_->digital_read(); @@ -76,43 +76,43 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() { return r; } -void ICACHE_RAM_ATTR ESPOneWire::write8(uint8_t val) { +void IRAM_ATTR ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void ICACHE_RAM_ATTR ESPOneWire::write64(uint64_t val) { +void IRAM_ATTR ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t ICACHE_RAM_ATTR ESPOneWire::read8() { +uint8_t IRAM_ATTR ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t ICACHE_RAM_ATTR ESPOneWire::read64() { +uint64_t IRAM_ATTR ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void ICACHE_RAM_ATTR ESPOneWire::select(uint64_t address) { +void IRAM_ATTR ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void ICACHE_RAM_ATTR ESPOneWire::reset_search() { +void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() { +uint64_t HOT IRAM_ATTR ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } @@ -196,7 +196,7 @@ uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() { return this->rom_number_; } -std::vector ICACHE_RAM_ATTR ESPOneWire::search_vec() { +std::vector IRAM_ATTR ESPOneWire::search_vec() { std::vector res; this->reset_search(); @@ -206,12 +206,12 @@ std::vector ICACHE_RAM_ATTR ESPOneWire::search_vec() { return res; } -void ICACHE_RAM_ATTR ESPOneWire::skip() { +void IRAM_ATTR ESPOneWire::skip() { this->write8(0xCC); // skip ROM } GPIOPin *ESPOneWire::get_pin() { return this->pin_; } -uint8_t ICACHE_RAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } +uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } } // namespace dallas } // namespace esphome diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index 68bcc0c193..728fa127d3 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -1,7 +1,8 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" +#include namespace esphome { namespace dallas { diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 11590a0978..7fd8956148 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -4,8 +4,18 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include +#include +#endif + +#ifdef USE_ARDUINO +#include +#endif + +#ifdef USE_ESP_IDF +#include +#include #endif namespace esphome { @@ -21,9 +31,14 @@ void DebugComponent::dump_config() { #endif ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); +#ifdef USE_ARDUINO this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP_IDF) + this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); +#ifdef USE_ARDUINO const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -38,7 +53,7 @@ void DebugComponent::dump_config() { case FM_DOUT: flash_mode = "DOUT"; break; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 case FM_FAST_READ: flash_mode = "FAST_READ"; break; @@ -52,8 +67,9 @@ void DebugComponent::dump_config() { // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, ESP.getFlashChipSpeed() / 1000000, flash_mode); +#endif // USE_ARDUINO -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_chip_info_t info; esp_chip_info(&info); const char *model; @@ -88,7 +104,9 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - std::string mac = uint64_to_string(ESP.getEfuseMac()); // NOLINT(readability-static-accessed-through-instance) + uint64_t chip_mac = 0LL; + esp_efuse_mac_get_default((uint8_t *) (&chip_mac)); + std::string mac = uint64_to_string(chip_mac); ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); const char *reset_reason; @@ -187,7 +205,7 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); #endif -#if defined(ARDUINO_ARCH_ESP8266) && !defined(CLANG_TIDY) +#if defined(USE_ESP8266) && !defined(CLANG_TIDY) ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); @@ -199,7 +217,11 @@ void DebugComponent::dump_config() { #endif } void DebugComponent::loop() { +#ifdef USE_ARDUINO uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP_IDF) + uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 1e07b75173..f47888b8eb 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -62,7 +62,7 @@ CONFIG_SCHEMA = cv.Schema( cv.Schema( { cv.Required(CONF_PINS): cv.ensure_list( - pins.shorthand_input_pin, validate_pin_number + pins.internal_gpio_input_pin_schema, validate_pin_number ), cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), } diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 9c354c8513..e4b1edfb7b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -2,6 +2,10 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#ifdef USE_ESP8266 +#include +#endif + namespace esphome { namespace deep_sleep { @@ -25,9 +29,9 @@ void DeepSleepComponent::dump_config() { if (this->run_duration_.has_value()) { ESP_LOGCONFIG(TAG, " Run Duration: %u ms", *this->run_duration_); } -#ifdef ARDUINO_ARCH_ESP32 - if (this->wakeup_pin_.has_value()) { - LOG_PIN(" Wakeup Pin: ", *this->wakeup_pin_); +#ifdef USE_ESP32 + if (wakeup_pin_ != nullptr) { + LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_); } #endif } @@ -39,7 +43,7 @@ float DeepSleepComponent::get_loop_priority() const { return -100.0f; // run after everything else is ready } void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } @@ -52,9 +56,9 @@ void DeepSleepComponent::begin_sleep(bool manual) { this->next_enter_deep_sleep_ = true; return; } -#ifdef ARDUINO_ARCH_ESP32 - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_.has_value() && - !this->sleep_duration_.has_value() && (*this->wakeup_pin_)->digital_read()) { +#ifdef USE_ESP32 + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr && + !this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) { // Defer deep sleep until inactive if (!this->next_enter_deep_sleep_) { this->status_set_warning(); @@ -69,14 +73,14 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); - if (this->wakeup_pin_.has_value()) { - bool level = !(*this->wakeup_pin_)->is_inverted(); - if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && (*this->wakeup_pin_)->digital_read()) + if (this->wakeup_pin_ != nullptr) { + bool level = this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) level = !level; - esp_sleep_enable_ext0_wakeup(gpio_num_t((*this->wakeup_pin_)->get_pin()), level); + esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); } if (this->ext1_wakeup_.has_value()) { esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode); @@ -90,7 +94,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_deep_sleep_start(); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif } diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 575f7be72b..d7969ba999 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -3,12 +3,16 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/automation.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" + +#ifdef USE_ESP32 +#include +#endif namespace esphome { namespace deep_sleep { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32 * and the scenario occurs that the wakeup pin is already in the wakeup state. @@ -44,11 +48,11 @@ class DeepSleepComponent : public Component { public: /// Set the duration in ms the component should sleep once it's in deep sleep mode. void set_sleep_duration(uint32_t time_ms); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 /** Set the pin to wake up to on the ESP32 once it's in deep sleep mode. * Use the inverted property to set the wakeup level. */ - void set_wakeup_pin(GPIOPin *pin) { this->wakeup_pin_ = pin; } + void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; } void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); @@ -72,8 +76,8 @@ class DeepSleepComponent : public Component { protected: optional sleep_duration_; -#ifdef ARDUINO_ARCH_ESP32 - optional wakeup_pin_; +#ifdef USE_ESP32 + InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; optional ext1_wakeup_; optional touch_wakeup_; diff --git a/esphome/components/demo/demo_sensor.h b/esphome/components/demo/demo_sensor.h index 9a35674124..b4afa03e11 100644 --- a/esphome/components/demo/demo_sensor.h +++ b/esphome/components/demo/demo_sensor.h @@ -13,7 +13,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent { float val = random_float(); bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING; if (increasing) { - float base = isnan(this->state) ? 0.0f : this->state; + float base = std::isnan(this->state) ? 0.0f : this->state; this->publish_state(base + val * 10); } else { if (val < 0.1) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 734dde20a8..2539bfe5ee 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -71,7 +71,7 @@ void DHT::set_dht_model(DHTModel model) { this->model_ = model; this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT; } -bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) { +bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) { *humidity = NAN; *temperature = NAN; @@ -83,7 +83,7 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, InterruptLock lock; this->pin_->digital_write(false); - this->pin_->pin_mode(OUTPUT); + this->pin_->pin_mode(gpio::FLAG_OUTPUT); this->pin_->digital_write(false); if (this->model_ == DHT_MODEL_DHT11) { @@ -99,7 +99,7 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, } else { delayMicroseconds(800); } - this->pin_->pin_mode(INPUT_PULLUP); + this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // Host pull up 20-40us then DHT response 80us // Start waiting for initial rising edge at the center when we diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 9b848cb119..f3a29f9ce9 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -36,7 +36,7 @@ class DHT : public PollingComponent { */ void set_dht_model(DHTModel model); - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_model(DHTModel model) { model_ = model; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -52,7 +52,7 @@ class DHT : public PollingComponent { protected: bool read_sensor_(float *temperature, float *humidity, bool report_errors); - GPIOPin *pin_; + InternalGPIOPin *pin_; DHTModel model_{DHT_MODEL_AUTO_DETECT}; bool is_auto_detect_{false}; sensor::Sensor *temperature_sensor_{nullptr}; diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index bbb7444cb5..2ee06e379f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -4,6 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/color.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace display { @@ -372,7 +373,7 @@ bool Glyph::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; const uint32_t pos = x_data + y_data * width_8; - return pgm_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); } const char *Glyph::get_char() const { return this->glyph_data_->a_char; } bool Glyph::compare_to(const char *str) const { @@ -464,22 +465,22 @@ bool Image::get_pixel(int x, int y) const { return false; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t pos = x + y * width_8; - return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); } Color Image::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t pos = (x + y * this->width_) * 3; - const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | - (pgm_read_byte(this->data_start_ + pos + 1) << 8) | - (pgm_read_byte(this->data_start_ + pos + 0) << 16); + const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | + (progmem_read_byte(this->data_start_ + pos + 1) << 8) | + (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; const uint32_t pos = (x + y * this->width_); - const uint8_t gray = pgm_read_byte(this->data_start_ + pos); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); } int Image::get_width() const { return this->width_; } @@ -496,7 +497,7 @@ bool Animation::get_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return false; const uint32_t pos = x + y * width_8 + frame_index; - return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); + return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); } Color Animation::get_color_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) @@ -505,9 +506,9 @@ Color Animation::get_color_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index) * 3; - const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) | - (pgm_read_byte(this->data_start_ + pos + 1) << 8) | - (pgm_read_byte(this->data_start_ + pos + 0) << 16); + const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | + (progmem_read_byte(this->data_start_ + pos + 1) << 8) | + (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } Color Animation::get_grayscale_pixel(int x, int y) const { @@ -517,7 +518,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) return Color::BLACK; const uint32_t pos = (x + y * this->width_ + frame_index); - const uint8_t gray = pgm_read_byte(this->data_start_ + pos); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); return Color(gray | gray << 8 | gray << 16 | gray << 24); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 3f89d3f8d2..54488f18f7 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "display_color_utils.h" +#include #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 1f1a2f980e..dd6e6051aa 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -39,14 +39,17 @@ def _validate_key(value): return "".join(f"{part:02X}" for part in parts_int) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(Dsmr), - cv.Optional(CONF_DECRYPTION_KEY): _validate_key, - cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, - cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, - } -).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Dsmr), + cv.Optional(CONF_DECRYPTION_KEY): _validate_key, + cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, + cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, + } + ).extend(uart.UART_DEVICE_SCHEMA), + cv.only_with_arduino, +) async def to_code(config): diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 7c1b406d42..54c4343cfe 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "dsmr.h" #include "esphome/core/log.h" @@ -128,8 +130,9 @@ void Dsmr::receive_encrypted_() { delay(4); // Wait for data } } - if (buffer_length > 0) + if (buffer_length > 0) { ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); + } } bool Dsmr::parse_telegram() { @@ -186,3 +189,5 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } // namespace dsmr } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index dcb8f5f73d..dfee3b338a 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" @@ -103,3 +105,5 @@ class Dsmr : public Component, public uart::UARTDevice { }; } // namespace dsmr } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index c989421948..3d7f731d5d 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -15,7 +15,7 @@ void DutyCycleSensor::setup() { this->last_update_ = micros(); this->store_.last_interrupt = micros(); - this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void DutyCycleSensor::dump_config() { LOG_SENSOR("", "Duty Cycle Sensor", this); @@ -44,8 +44,8 @@ void DutyCycleSensor::update() { float DutyCycleSensor::get_setup_priority() const { return setup_priority::DATA; } -void ICACHE_RAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) { - const bool new_level = arg->pin->digital_read(); +void IRAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) { + const bool new_level = arg->pin.digital_read(); if (new_level == arg->last_level) return; arg->last_level = new_level; diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index e168f20eff..22d3588fb7 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -12,14 +12,14 @@ struct DutyCycleSensorStore { volatile uint32_t last_interrupt{0}; volatile uint32_t on_time{0}; volatile bool last_level{false}; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; static void gpio_intr(DutyCycleSensorStore *arg); }; class DutyCycleSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void setup() override; float get_setup_priority() const override; @@ -27,7 +27,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { void update() override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; DutyCycleSensorStore store_{}; uint32_t last_update_; diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 3537cb0973..6a367328e6 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -25,9 +25,7 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(DutyCycleSensor), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index 5eb823064d..bb662e0989 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -4,6 +4,8 @@ from esphome.components.light.types import AddressableLightEffect from esphome.components.light.effects import register_addressable_effect from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS +DEPENDENCIES = ["network"] + e131_ns = cg.esphome_ns.namespace("e131") E131AddressableLightEffect = e131_ns.class_( "E131AddressableLightEffect", AddressableLightEffect @@ -21,11 +23,16 @@ CHANNELS = { CONF_UNIVERSE = "universe" CONF_E131_ID = "e131_id" -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(E131Component), - cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True), - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(E131Component), + cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of( + *METHODS, upper=True + ), + } + ), + cv.only_with_arduino, ) diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index 7694d039e5..35510fe204 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "e131_addressable_light_effect.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -104,3 +106,5 @@ bool E131Component::process_(int universe, const E131Packet &packet) { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index 3f647edbf1..3819e522a5 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include @@ -55,3 +57,5 @@ class E131Component : public esphome::Component { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index f0f165b25f..371f3b9cbf 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "e131_addressable_light_effect.h" #include "esphome/core/log.h" @@ -90,3 +92,5 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_addressable_light_effect.h b/esphome/components/e131/e131_addressable_light_effect.h index 1ab5d43164..e78f6bb0e0 100644 --- a/esphome/components/e131/e131_addressable_light_effect.h +++ b/esphome/components/e131/e131_addressable_light_effect.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/light/addressable_light_effect.h" @@ -46,3 +48,5 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/e131/e131_packet.cpp b/esphome/components/e131/e131_packet.cpp index 14fdc084a6..b20eb9f666 100644 --- a/esphome/components/e131/e131_packet.cpp +++ b/esphome/components/e131/e131_packet.cpp @@ -1,8 +1,14 @@ +#ifdef USE_ARDUINO + #include "e131.h" #include "esphome/core/log.h" #include "esphome/core/util.h" +#include "esphome/components/network/ip_address.h" +#include +#include #include +#include #include namespace esphome { @@ -63,8 +69,8 @@ bool E131Component::join_igmp_groups_() { if (!universe.second) continue; - ip4_addr_t multicast_addr = { - static_cast(IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))}; + ip4_addr_t multicast_addr = {static_cast( + network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))}; auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr); @@ -98,7 +104,7 @@ void E131Component::leave_(int universe) { if (listen_method_ == E131_MULTICAST) { ip4_addr_t multicast_addr = { - static_cast(IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))}; + static_cast(network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))}; igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr); } @@ -134,3 +140,5 @@ bool E131Component::packet_(const std::vector &data, int &universe, E13 } // namespace e131 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index cbc4b334d9..67c6a4ebd3 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -1,5 +1,6 @@ #include "endstop_cover.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace endstop { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py new file mode 100644 index 0000000000..c86087cc25 --- /dev/null +++ b/esphome/components/esp32/__init__.py @@ -0,0 +1,379 @@ +from dataclasses import dataclass +from typing import Union +from pathlib import Path +import logging + +from esphome.helpers import write_file_if_changed +from esphome.const import ( + CONF_BOARD, + CONF_FRAMEWORK, + CONF_TYPE, + CONF_VARIANT, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE, HexInt +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_SDKCONFIG_OPTIONS, + KEY_VARIANT, + VARIANT_ESP32C3, + VARIANTS, +) + +# force import gpio to register pin schema +from .gpio import esp32_pin_to_code # noqa + + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@esphome/core"] + + +def set_core_data(config): + CORE.data[KEY_ESP32] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp32" + conf = config[CONF_FRAMEWORK] + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" + CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION_HINT] + ) + CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] + return config + + +def get_esp32_variant(): + return CORE.data[KEY_ESP32][KEY_VARIANT] + + +def is_esp32c3(): + return get_esp32_variant() == VARIANT_ESP32C3 + + +@dataclass +class RawSdkconfigValue: + """An sdkconfig value that won't be auto-formatted""" + + value: str + + +SdkconfigValueType = Union[bool, int, HexInt, str, RawSdkconfigValue] + + +def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): + """Set an esp-idf sdkconfig value.""" + if not CORE.using_esp_idf: + raise ValueError("Not an esp-idf project") + CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to + # a PIO platformio/framework-arduinoespressif32 value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 + if ver <= cv.Version(1, 0, 3): + return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * New framework historically have had some regressions, especially for WiFi. +# The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# The default/recommended arduino framework version +# - https://github.com/espressif/arduino-esp32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6) +# The platformio/espressif32 version to use for arduino frameworks +# - https://github.com/platformio/platform-espressif32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 +ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2) + +# The default/recommended esp-idf framework version +# - https://github.com/espressif/esp-idf/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0) +# The platformio/espressif32 version to use for esp-idf frameworks +# - https://github.com/platformio/platform-espressif32/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 +ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)), + "latest": ("", cv.Version(1, 0, 3)), + "recommended": ( + _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), + RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + if ver <= cv.Version(1, 0, 3): + ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + else: + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected arduino framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + return value + + +def _format_framework_espidf_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/esp-idf/releases) version to + # a PIO platformio/framework-espidf value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +def _esp_idf_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)), + "latest": ("", cv.Version(4, 3, 0)), + "recommended": ( + _format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION), + RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0): + raise cv.Invalid("Only ESP-IDF 4.0+ is supported") + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected esp-idf framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + return value + + +CONF_VERSION_HINT = "version_hint" +CONF_PLATFORM_VERSION = "platform_version" +ARDUINO_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _arduino_check_versions, +) +CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" +ESP_IDF_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { + cv.string_strict: cv.string_strict + }, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _esp_idf_check_versions, +) + + +FRAMEWORK_ESP_IDF = "esp-idf" +FRAMEWORK_ARDUINO = "arduino" +FRAMEWORK_SCHEMA = cv.typed_schema( + { + FRAMEWORK_ESP_IDF: ESP_IDF_FRAMEWORK_SCHEMA, + FRAMEWORK_ARDUINO: ARDUINO_FRAMEWORK_SCHEMA, + }, + lower=True, + space="-", + default_type=FRAMEWORK_ARDUINO, +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of( + *VARIANTS, upper=True + ), + cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, + } + ), + set_core_data, +) + + +async def to_code(config): + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_ESP32") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") + + conf = config[CONF_FRAMEWORK] + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: + cg.add_platformio_option( + "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" + ) + cg.add_platformio_option("framework", "espidf") + cg.add_build_flag("-DUSE_ESP_IDF") + cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") + cg.add_build_flag("-Wno-nonnull-compare") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-espidf @ {conf[CONF_VERSION]}"], + ) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) + add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) + add_idf_sdkconfig_option( + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv" + ) + add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) + add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + + cg.add_platformio_option("board_build.partitions", "partitions.csv") + + for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): + add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) + + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: + cg.add_platformio_option( + "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" + ) + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"], + ) + + cg.add_platformio_option("board_build.partitions", "partitions.csv") + + +ARDUINO_PARTITIONS_CSV = """\ +nvs, data, nvs, 0x009000, 0x005000, +otadata, data, ota, 0x00e000, 0x002000, +app0, app, ota_0, 0x010000, 0x1C0000, +app1, app, ota_1, 0x1D0000, 0x1C0000, +eeprom, data, 0x99, 0x390000, 0x001000, +spiffs, data, spiffs, 0x391000, 0x00F000 +""" + + +IDF_PARTITIONS_CSV = """\ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, , 0x4000, +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +app0, app, ota_0, , 0x1C0000, +app1, app, ota_1, , 0x1C0000, +""" + + +def _format_sdkconfig_val(value: SdkconfigValueType) -> str: + if isinstance(value, bool): + return "y" if value else "n" + if isinstance(value, int): + return str(value) + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, RawSdkconfigValue): + return value.value + raise ValueError + + +def _write_sdkconfig(): + # sdkconfig.{name} stores the real sdkconfig (modified by esp-idf with default) + # sdkconfig.{name}.esphomeinternal stores what esphome last wrote + # we use the internal one to detect if there were any changes, and if so write them to the + # real sdkconfig + sdk_path = Path(CORE.relative_build_path(f"sdkconfig.{CORE.name}")) + internal_path = Path( + CORE.relative_build_path(f"sdkconfig.{CORE.name}.esphomeinternal") + ) + + want_opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + contents = ( + "\n".join( + f"{name}={_format_sdkconfig_val(value)}" + for name, value in sorted(want_opts.items()) + ) + + "\n" + ) + if write_file_if_changed(internal_path, contents): + # internal changed, update real one + write_file_if_changed(sdk_path, contents) + + +# Called by writer.py +def copy_files(): + if CORE.using_arduino: + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + ARDUINO_PARTITIONS_CSV, + ) + if CORE.using_esp_idf: + _write_sdkconfig() + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + IDF_PARTITIONS_CSV, + ) diff --git a/esphome/boards.py b/esphome/components/esp32/boards.py similarity index 77% rename from esphome/boards.py rename to esphome/components/esp32/boards.py index ba6fe889ea..ddf4bf2026 100644 --- a/esphome/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,212 +1,3 @@ -ESP8266_BASE_PINS = { - "A0": 17, - "SS": 15, - "MOSI": 13, - "MISO": 12, - "SCK": 14, - "SDA": 4, - "SCL": 5, - "RX": 3, - "TX": 1, -} - -ESP8266_BOARD_PINS = { - "d1": { - "D0": 3, - "D1": 1, - "D2": 16, - "D3": 5, - "D4": 4, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 0, - "D9": 2, - "D10": 15, - "D11": 13, - "D12": 14, - "D13": 14, - "D14": 4, - "D15": 5, - "LED": 2, - }, - "d1_mini": { - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "LED": 2, - }, - "d1_mini_lite": "d1_mini", - "d1_mini_pro": "d1_mini", - "esp01": {}, - "esp01_1m": {}, - "esp07": {}, - "esp12e": {}, - "esp210": {}, - "esp8285": {}, - "esp_wroom_02": {}, - "espduino": {"LED": 16}, - "espectro": {"LED": 15, "BUTTON": 2}, - "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, - "espinotee": {"LED": 16}, - "espmxdevkit": {}, - "espresso_lite_v1": {"LED": 16}, - "espresso_lite_v2": {"LED": 2}, - "gen4iod": {}, - "heltec_wifi_kit_8": "d1_mini", - "huzzah": { - "LED": 0, - "LED_RED": 0, - "LED_BLUE": 2, - "D4": 4, - "D5": 5, - "D12": 12, - "D13": 13, - "D14": 14, - "D15": 15, - "D16": 16, - }, - "inventone": {}, - "modwifi": {}, - "nodemcu": { - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 3, - "D10": 1, - "LED": 16, - }, - "nodemcuv2": "nodemcu", - "oak": { - "P0": 2, - "P1": 5, - "P2": 0, - "P3": 3, - "P4": 1, - "P5": 4, - "P6": 15, - "P7": 13, - "P8": 12, - "P9": 14, - "P10": 16, - "P11": 17, - "LED": 5, - }, - "phoenix_v1": {"LED": 16}, - "phoenix_v2": {"LED": 2}, - "sonoff_basic": {}, - "sonoff_s20": {}, - "sonoff_sv": {}, - "sonoff_th": {}, - "sparkfunBlynk": "thing", - "thing": {"LED": 5, "SDA": 2, "SCL": 14}, - "thingdev": "thing", - "wifi_slot": {"LED": 2}, - "wifiduino": { - "D0": 3, - "D1": 1, - "D2": 2, - "D3": 0, - "D4": 4, - "D5": 5, - "D6": 16, - "D7": 14, - "D8": 12, - "D9": 13, - "D10": 15, - "D11": 13, - "D12": 12, - "D13": 14, - }, - "wifinfo": { - "LED": 12, - "D0": 16, - "D1": 5, - "D2": 4, - "D3": 0, - "D4": 2, - "D5": 14, - "D6": 12, - "D7": 13, - "D8": 15, - "D9": 3, - "D10": 1, - }, - "wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0}, - "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, - "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, -} - -FLASH_SIZE_1_MB = 2 ** 20 -FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 -FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB -FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB -FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB - -ESP8266_FLASH_SIZES = { - "d1": FLASH_SIZE_4_MB, - "d1_mini": FLASH_SIZE_4_MB, - "d1_mini_lite": FLASH_SIZE_1_MB, - "d1_mini_pro": FLASH_SIZE_16_MB, - "esp01": FLASH_SIZE_512_KB, - "esp01_1m": FLASH_SIZE_1_MB, - "esp07": FLASH_SIZE_4_MB, - "esp12e": FLASH_SIZE_4_MB, - "esp210": FLASH_SIZE_4_MB, - "esp8285": FLASH_SIZE_1_MB, - "esp_wroom_02": FLASH_SIZE_2_MB, - "espduino": FLASH_SIZE_4_MB, - "espectro": FLASH_SIZE_4_MB, - "espino": FLASH_SIZE_4_MB, - "espinotee": FLASH_SIZE_4_MB, - "espmxdevkit": FLASH_SIZE_1_MB, - "espresso_lite_v1": FLASH_SIZE_4_MB, - "espresso_lite_v2": FLASH_SIZE_4_MB, - "gen4iod": FLASH_SIZE_512_KB, - "heltec_wifi_kit_8": FLASH_SIZE_4_MB, - "huzzah": FLASH_SIZE_4_MB, - "inventone": FLASH_SIZE_4_MB, - "modwifi": FLASH_SIZE_2_MB, - "nodemcu": FLASH_SIZE_4_MB, - "nodemcuv2": FLASH_SIZE_4_MB, - "oak": FLASH_SIZE_4_MB, - "phoenix_v1": FLASH_SIZE_4_MB, - "phoenix_v2": FLASH_SIZE_4_MB, - "sonoff_basic": FLASH_SIZE_1_MB, - "sonoff_s20": FLASH_SIZE_1_MB, - "sonoff_sv": FLASH_SIZE_1_MB, - "sonoff_th": FLASH_SIZE_1_MB, - "sparkfunBlynk": FLASH_SIZE_4_MB, - "thing": FLASH_SIZE_512_KB, - "thingdev": FLASH_SIZE_512_KB, - "wifi_slot": FLASH_SIZE_1_MB, - "wifiduino": FLASH_SIZE_4_MB, - "wifinfo": FLASH_SIZE_1_MB, - "wio_link": FLASH_SIZE_4_MB, - "wio_node": FLASH_SIZE_4_MB, - "xinabox_cw01": FLASH_SIZE_4_MB, -} - -ESP8266_LD_SCRIPTS = { - FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), - FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), - FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), - FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), - FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), -} - ESP32_BASE_PINS = { "TX": 1, "RX": 3, @@ -1134,19 +925,3 @@ ESP32_BOARD_PINS = { }, "xinabox_cw02": {"LED": 27}, } - -ESP32_C3_BASE_PINS = { - "TX": 21, - "RX": 20, - "ADC1_0": 0, - "ADC1_1": 1, - "ADC1_2": 2, - "ADC1_3": 3, - "ADC1_4": 4, - "ADC2_0": 5, -} - -ESP32_C3_BOARD_PINS = { - "esp32-c3-devkitm-1": {"LED": 8}, - "esp32-c3-devkitc-02": "esp32-c3-devkitm-1", -} diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py new file mode 100644 index 0000000000..b82f03bf68 --- /dev/null +++ b/esphome/components/esp32/const.py @@ -0,0 +1,21 @@ +import esphome.codegen as cg + +KEY_ESP32 = "esp32" +KEY_BOARD = "board" +KEY_VARIANT = "variant" +KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" + +VARIANT_ESP32 = "ESP32" +VARIANT_ESP32S2 = "ESP32S2" +VARIANT_ESP32S3 = "ESP32S3" +VARIANT_ESP32C3 = "ESP32C3" +VARIANT_ESP32H2 = "ESP32H2" +VARIANTS = [ + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, + VARIANT_ESP32H2, +] + +esp32_ns = cg.esphome_ns.namespace("esp32") diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp new file mode 100644 index 0000000000..96047df535 --- /dev/null +++ b/esphome/components/esp32/core.cpp @@ -0,0 +1,89 @@ +#ifdef USE_ESP32 + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" +#include +#include +#include +#include + +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#endif + +void setup(); +void loop(); + +namespace esphome { + +void IRAM_ATTR HOT yield() { vPortYield(); } +uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } +void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } +uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { + auto start = (uint64_t) esp_timer_get_time(); + while (((uint64_t) esp_timer_get_time()) - start < us) + ; +} +void arch_restart() { + esp_restart(); + // restart() doesn't always end execution + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void IRAM_ATTR HOT arch_feed_wdt() { +#ifdef USE_ARDUINO +#if CONFIG_ARDUINO_RUNNING_CORE == 0 +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. + // To cause the Watchdog to be triggered we need to put the current task + // to sleep to get the idle task scheduled. + delay(1); +#endif +#endif +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 + delay(1); +#endif +#endif // USE_ESP_IDF +} + +uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } +uint32_t arch_get_cpu_cycle_count() { +#if ESP_IDF_VERSION_MAJOR >= 4 + return cpu_hal_get_cycle_count(); +#else + uint32_t ccount; + __asm__ __volatile__("esync; rsr %0,ccount" : "=a"(ccount)); + return ccount; +#endif +} +uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); } + +#ifdef USE_ESP_IDF +TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +void loop_task(void *pv_params) { + setup(); + while (true) { + loop(); + } +} + +extern "C" void app_main() { + esp32::setup_preferences(); + xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle); +} +#endif // USE_ESP_IDF + +#ifdef USE_ARDUINO +extern "C" void init() { esp32::setup_preferences(); } +#endif // USE_ARDUINO + +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py new file mode 100644 index 0000000000..93ab17db22 --- /dev/null +++ b/esphome/components/esp32/gpio.py @@ -0,0 +1,201 @@ +import logging + +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome import pins +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from . import boards +from .const import KEY_BOARD, KEY_ESP32, esp32_ns + + +_LOGGER = logging.getLogger(__name__) + + +IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) +ArduinoInternalGPIOPin = esp32_ns.class_("ArduinoInternalGPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_ESP32][KEY_BOARD] + board_pins = boards.ESP32_BOARD_PINS.get(board, {}) + + # Resolved aliased board pins (shorthand when two boards have the same pin configuration) + while isinstance(board_pins, str): + board_pins = boards.ESP32_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.ESP32_BASE_PINS: + return boards.ESP32_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 39: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + if value in (20, 24, 28, 29, 30, 31): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") + return value + + +def validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_open_drain = mode[CONF_OPEN_DRAIN] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if is_input: + # All ESP32 pins support input mode + pass + if is_output and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_open_drain and not is_output: + raise cv.Invalid( + "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] + ) + if is_pullup and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + if CORE.using_arduino: + # (input, output, open_drain, pullup, pulldown) + supported_modes = { + # INPUT + (True, False, False, False, False), + # OUTPUT + (False, True, False, False, False), + # INPUT_PULLUP + (True, False, False, True, False), + # INPUT_PULLDOWN + (True, False, False, False, True), + # OUTPUT_OPEN_DRAIN + (False, True, True, False, False), + } + key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown) + if key not in supported_modes: + raise cv.Invalid( + "This pin mode is not supported on ESP32 for arduino frameworks", + [CONF_MODE], + ) + + return value + + +# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-reference/peripherals/gpio.html#_CPPv416gpio_drive_cap_t +gpio_drive_cap_t = cg.global_ns.enum("gpio_drive_cap_t") +DRIVE_STRENGTHS = { + 5.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_0, + 10.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_1, + 20.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_2, + 40.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_3, +} +gpio_num_t = cg.global_ns.enum("gpio_num_t") + + +def _choose_pin_declaration(value): + if CORE.using_esp_idf: + return cv.declare_id(IDFInternalGPIOPin)(value) + if CORE.using_arduino: + return cv.declare_id(ArduinoInternalGPIOPin)(value) + raise NotImplementedError + + +CONF_DRIVE_STRENGTH = "drive_strength" +ESP32_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): _choose_pin_declaration, + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.SplitDefault(CONF_DRIVE_STRENGTH, esp32_idf="20mA"): cv.All( + cv.only_with_esp_idf, + cv.float_with_unit("current", "mA", optional_unit=True), + cv.enum(DRIVE_STRENGTHS), + ), + }, + validate_supports, +) + + +@pins.PIN_SCHEMA_REGISTRY.register("esp32", ESP32_PIN_SCHEMA) +async def esp32_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + if CORE.using_esp_idf: + cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) + else: + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + if CONF_DRIVE_STRENGTH in config: + cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp new file mode 100644 index 0000000000..11de1e13e6 --- /dev/null +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -0,0 +1,107 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "gpio_arduino.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32"; + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { + uint8_t arduino_mode = DISABLED; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? ONHIGH : ONLOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? ONLOW : ONHIGH; + break; + } + + attachInterruptArg(pin_, func, arg, arduino_mode); +} +void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { + uint8_t mode; + if (flags == gpio::FLAG_INPUT) { + mode = INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + mode = OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + mode = INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + mode = INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + mode = OUTPUT_OPEN_DRAIN; + } else { + return; + } + pinMode(pin_, mode); // NOLINT +} + +std::string ArduinoInternalGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool ArduinoInternalGPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void ArduinoInternalGPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void ArduinoInternalGPIOPin::detach_interrupt() const { + detachInterrupt(pin_); // NOLINT +} + +} // namespace esp32 + +using namespace esp32; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); +#ifdef CONFIG_IDF_TARGET_ESP32C3 + GPIO.status_w1tc.val = 1UL << arg->pin; +#else + if (arg->pin < 32) { + GPIO.status_w1tc = 1UL << arg->pin; + } else { + GPIO.status1_w1tc.intr_st = 1UL << (arg->pin - 32); + } +#endif +} + +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/esp32/gpio_arduino.h b/esphome/components/esp32/gpio_arduino.h new file mode 100644 index 0000000000..a077723075 --- /dev/null +++ b/esphome/components/esp32/gpio_arduino.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include "esphome/core/hal.h" + +namespace esphome { +namespace esp32 { + +class ArduinoInternalGPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp new file mode 100644 index 0000000000..d662d5519a --- /dev/null +++ b/esphome/components/esp32/gpio_idf.cpp @@ -0,0 +1,49 @@ +#ifdef USE_ESP32_FRAMEWORK_ESP_IDF + +#include "gpio_idf.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32"; + +bool IDFInternalGPIOPin::isr_service_installed_ = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +struct ISRPinArg { + gpio_num_t pin; + bool inverted; +}; + +ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +std::string IDFInternalGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast(pin_)); + return buffer; +} + +} // namespace esp32 + +using namespace esp32; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(gpio_get_level(arg->pin)) != arg->inverted; +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0); +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + // not supported +} + +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ESP_IDF diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h new file mode 100644 index 0000000000..6a383afcae --- /dev/null +++ b/esphome/components/esp32/gpio_idf.h @@ -0,0 +1,96 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ESP_IDF +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace esp32 { + +class IDFInternalGPIOPin : public InternalGPIOPin { + public: + void set_pin(gpio_num_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { + pin_mode(flags_); + gpio_set_drive_capability(pin_, drive_strength_); + } + void pin_mode(gpio::Flags flags) override { + gpio_config_t conf{}; + conf.pin_bit_mask = 1 << static_cast(pin_); + conf.mode = flags_to_mode_(flags); + conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&conf); + } + bool digital_read() override { return bool(gpio_get_level(pin_)) != inverted_; } + void digital_write(bool value) override { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } + std::string dump_summary() const override; + void detach_interrupt() const override { gpio_intr_disable(pin_); } + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return (uint8_t) pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + static gpio_mode_t flags_to_mode_(gpio::Flags flags) { + flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); + if (flags == gpio::FLAG_NONE) { + return GPIO_MODE_DISABLE; + } else if (flags == gpio::FLAG_INPUT) { + return GPIO_MODE_INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return GPIO_MODE_OUTPUT; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return GPIO_MODE_INPUT_OUTPUT_OD; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) { + return GPIO_MODE_INPUT_OUTPUT; + } else { + // unsupported + return GPIO_MODE_DISABLE; + } + } + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override { + gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE; + break; + case gpio::INTERRUPT_FALLING_EDGE: + idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE; + break; + case gpio::INTERRUPT_ANY_EDGE: + idf_type = GPIO_INTR_ANYEDGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL; + break; + } + gpio_set_intr_type(pin_, idf_type); + gpio_intr_enable(pin_); + if (!isr_service_installed_) { + gpio_install_isr_service(ESP_INTR_FLAG_LEVEL5); + isr_service_installed_ = true; + } + gpio_isr_handler_add(pin_, func, arg); + } + + gpio_num_t pin_; + bool inverted_; + gpio_drive_cap_t drive_strength_; + gpio::Flags flags_; + static bool isr_service_installed_; +}; + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ESP_IDF diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp new file mode 100644 index 0000000000..639e6434d6 --- /dev/null +++ b/esphome/components/esp32/preferences.cpp @@ -0,0 +1,99 @@ +#ifdef USE_ESP32 + +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace esp32 { + +static const char *const TAG = "esp32.preferences"; + +class ESP32PreferenceBackend : public ESPPreferenceBackend { + public: + std::string key; + uint32_t nvs_handle; + bool save(const uint8_t *data, size_t len) override { + esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); + return false; + } + err = nvs_commit(nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); + return false; + } + return true; + } + bool load(uint8_t *data, size_t len) override { + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err)); + return false; + } + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len); + return false; + } + err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); + return false; + } + return true; + } +}; + +class ESP32Preferences : public ESPPreferences { + public: + uint32_t nvs_handle; + uint32_t current_offset = 0; + + void open() { + esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); + if (err == 0) + return; + + ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err)); + nvs_flash_deinit(); + nvs_flash_erase(); + nvs_flash_init(); + + err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); + if (err != 0) { + nvs_handle = 0; + } + } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + return make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->nvs_handle = nvs_handle; + current_offset += length; + + uint32_t keyval = current_offset ^ type; + char keybuf[16]; + snprintf(keybuf, sizeof(keybuf), "%d", keyval); + pref->key = keybuf; // copied to std::string + + return ESPPreferenceObject(pref); + } +}; + +void setup_preferences() { + auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + prefs->open(); + global_preferences = prefs; +} + +} // namespace esp32 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.h b/esphome/components/esp32/preferences.h new file mode 100644 index 0000000000..e44213e4cf --- /dev/null +++ b/esphome/components/esp32/preferences.h @@ -0,0 +1,12 @@ +#pragma once +#ifdef USE_ESP32 + +namespace esphome { +namespace esp32 { + +void setup_preferences(); + +} // namespace esp32 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ccf1f6cafe..4b5c741ad9 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,8 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] @@ -20,3 +22,6 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 2d710a6ef7..143be06e3b 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,17 +1,21 @@ -#include "ble.h" +#ifdef USE_ESP32 +#include "ble.h" #include "esphome/core/application.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 - #include #include #include #include +#include #include #include +#ifdef USE_ARDUINO +#include +#endif + namespace esphome { namespace esp32_ble { @@ -52,9 +56,29 @@ bool ESP32BLE::ble_setup_() { return false; } - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return false; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return false; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return false; + } } esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 149f0008a4..008eba3235 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -11,7 +11,7 @@ #include "esphome/components/esp32_ble_server/ble_server.h" #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index bcfb0d1a1b..31b1f4c383 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -1,8 +1,11 @@ #include "ble_advertising.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "ble_uuid.h" +#include +#include +#include "esphome/core/log.h" namespace esphome { namespace esp32_ble { diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index d86089f333..01e2ba1295 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -2,7 +2,7 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index b33275110d..8556aa87df 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -1,6 +1,10 @@ #include "ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 + +#include +#include +#include "esphome/core/log.h" namespace esphome { namespace esp32_ble { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 89082e19fa..f953f9fede 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -1,9 +1,9 @@ #pragma once #include "esphome/core/helpers.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index cd123d5469..8fb2803237 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -1,15 +1,19 @@ #pragma once + +#ifdef USE_ESP32 + #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include #include - -#ifdef ARDUINO_ARCH_ESP32 +#include #include #include #include +#include +#include /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index cd0cb4bed6..d6cbb15dd2 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -1,8 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CONFLICTS_WITH = ["esp32_ble_tracker"] esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") @@ -29,3 +31,6 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_minor(config[CONF_MINOR])) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index c9e00a1093..96afadd19a 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -1,14 +1,16 @@ #include "esp32_ble_beacon.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include -#include +#include #include #include #include #include +#include +#include "esphome/core/hal.h" namespace esphome { namespace esp32_ble_beacon { @@ -59,6 +61,7 @@ void ESP32BLEBeacon::ble_core_task(void *params) { delay(1000); // NOLINT } } + void ESP32BLEBeacon::ble_setup() { // Initialize non-volatile storage for the bluetooth controller esp_err_t err = nvs_flash_init(); @@ -67,9 +70,29 @@ void ESP32BLEBeacon::ble_setup() { return; } - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return; + } } esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index aba02830b3..d0ef73899c 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 0dcbcadc50..2fcc5c7743 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -1,12 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_MODEL from esphome.components import esp32_ble +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] CONF_MANUFACTURER = "manufacturer" CONF_BLE_ID = "ble_id" @@ -37,3 +39,6 @@ async def to_code(config): cg.add_define("USE_ESP32_BLE_SERVER") cg.add(parent.set_server(var)) + + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble_server/ble_2901.cpp b/esphome/components/esp32_ble_server/ble_2901.cpp index 962ba3ffbe..ee0808d2c4 100644 --- a/esphome/components/esp32_ble_server/ble_2901.cpp +++ b/esphome/components/esp32_ble_server/ble_2901.cpp @@ -1,7 +1,7 @@ #include "ble_2901.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2901.h b/esphome/components/esp32_ble_server/ble_2901.h index 3bb23ae69d..60f53e55b2 100644 --- a/esphome/components/esp32_ble_server/ble_2901.h +++ b/esphome/components/esp32_ble_server/ble_2901.h @@ -2,7 +2,7 @@ #include "ble_descriptor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2902.cpp b/esphome/components/esp32_ble_server/ble_2902.cpp index 0a87b239f9..2f34573c37 100644 --- a/esphome/components/esp32_ble_server/ble_2902.cpp +++ b/esphome/components/esp32_ble_server/ble_2902.cpp @@ -1,7 +1,9 @@ #include "ble_2902.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 + +#include namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_2902.h b/esphome/components/esp32_ble_server/ble_2902.h index 024eec755e..64605924ad 100644 --- a/esphome/components/esp32_ble_server/ble_2902.h +++ b/esphome/components/esp32_ble_server/ble_2902.h @@ -2,7 +2,7 @@ #include "ble_descriptor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index d8ff39cc94..fae8c13934 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -4,7 +4,7 @@ #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h index bc5033f2ae..d2467dd176 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -5,13 +5,15 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include #include #include #include +#include +#include namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 5262087ad1..bfb6224335 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -1,10 +1,11 @@ #include "ble_descriptor.h" #include "ble_characteristic.h" #include "ble_service.h" - #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#include + +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h index 1a72cb2b54..4b8fb345c3 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -2,7 +2,7 @@ #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 5777e99e23..0b91c238c3 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -5,7 +5,7 @@ #include "esphome/core/application.h" #include "esphome/core/version.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 9d955dda79..7df44b4c61 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -12,7 +12,7 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 5cfc53a397..281164f0f5 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -2,7 +2,7 @@ #include "ble_server.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_server { diff --git a/esphome/components/esp32_ble_server/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h index 9fdb95bde5..16cc897238 100644 --- a/esphome/components/esp32_ble_server/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -3,7 +3,7 @@ #include "ble_characteristic.h" #include "esphome/components/esp32_ble/ble_uuid.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 162a4dee89..e3d52f345a 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -4,7 +4,6 @@ import esphome.config_validation as cv from esphome import automation from esphome.const import ( CONF_ID, - ESP_PLATFORM_ESP32, CONF_INTERVAL, CONF_DURATION, CONF_TRIGGER_ID, @@ -15,8 +14,10 @@ from esphome.const import ( CONF_ON_BLE_SERVICE_DATA_ADVERTISE, CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, ) +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] CONF_ESP32_BLE_ID = "esp32_ble_id" @@ -216,6 +217,9 @@ async def to_code(config): cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) + if CORE.using_esp_idf: + add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + async def register_ble_device(var, config): paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 9df2587ede..3505e9c26d 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_ble_tracker { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 358fe50605..5568884b9a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -1,18 +1,24 @@ +#ifdef USE_ESP32 + #include "esp32_ble_tracker.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" #include #include #include #include +#include #include #include #include +#ifdef USE_ARDUINO +#include +#endif + // bt_trace.h #undef TAG @@ -126,14 +132,33 @@ bool ESP32BLETracker::ble_setup() { return false; } - esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); - - // Initialize the bluetooth controller with the default configuration - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + // start bt controller + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { + esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); + return false; + } + while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) + ; + } + if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { + err = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); + return false; + } + } + if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { + ESP_LOGE(TAG, "esp bt controller enable failed"); + return false; + } } + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + err = esp_bluedroid_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 0594c4a811..40955d39cf 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -4,7 +4,7 @@ #include "esphome/core/helpers.h" #include "queue.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include diff --git a/esphome/components/esp32_ble_tracker/queue.h b/esphome/components/esp32_ble_tracker/queue.h index f0f6ab9f17..3d38c17584 100644 --- a/esphome/components/esp32_ble_tracker/queue.h +++ b/esphome/components/esp32_ble_tracker/queue.h @@ -1,14 +1,17 @@ #pragma once + +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include #include - -#ifdef ARDUINO_ARCH_ESP32 +#include #include #include +#include +#include /* * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index e3c7383953..de61ab43cf 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -9,16 +9,16 @@ from esphome.const import ( CONF_PIN, CONF_SCL, CONF_SDA, - ESP_PLATFORM_ESP32, CONF_DATA_PINS, CONF_RESET_PIN, CONF_RESOLUTION, CONF_BRIGHTNESS, CONF_CONTRAST, ) +from esphome.core import CORE +from esphome.components.esp32 import add_idf_sdkconfig_option -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ["api"] +DEPENDENCIES = ["esp32", "api"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.Nameable) @@ -68,13 +68,15 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(ESP32Camera), cv.Required(CONF_NAME): cv.string, cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean, - cv.Required(CONF_DATA_PINS): cv.All([pins.input_pin], cv.Length(min=8, max=8)), - cv.Required(CONF_VSYNC_PIN): pins.input_pin, - cv.Required(CONF_HREF_PIN): pins.input_pin, - cv.Required(CONF_PIXEL_CLOCK_PIN): pins.input_pin, + cv.Required(CONF_DATA_PINS): cv.All( + [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) + ), + cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( { - cv.Required(CONF_PIN): pins.output_pin, + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( cv.frequency, cv.one_of(20e6, 10e6) ), @@ -82,12 +84,12 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Required(CONF_I2C_PINS): cv.Schema( { - cv.Required(CONF_SDA): pins.output_pin, - cv.Required(CONF_SCL): pins.output_pin, + cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, + cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, } ), - cv.Optional(CONF_RESET_PIN): pins.output_pin, - cv.Optional(CONF_POWER_DOWN_PIN): pins.output_pin, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( cv.framerate, cv.Range(min=0, min_included=False, max=60) ), @@ -146,3 +148,8 @@ async def to_code(config): cg.add_define("USE_ESP32_CAMERA") cg.add_build_flag("-DBOARD_HAS_PSRAM") + + if CORE.using_esp_idf: + cg.add_library("espressif/esp32-camera", "1.0.0") + add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 072cf41460..05445f024b 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -1,7 +1,10 @@ +#ifdef USE_ESP32 + #include "esp32_camera.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" -#ifdef ARDUINO_ARCH_ESP32 +#include namespace esphome { namespace esp32_camera { @@ -42,7 +45,9 @@ void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); +#ifdef USE_ARDUINO ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); +#endif // USE_ARDUINO ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7); ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync); diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 03272d3b32..430391aa76 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -1,10 +1,12 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include +#include +#include namespace esphome { namespace esp32_camera { diff --git a/esphome/components/esp32_dac/esp32_dac.cpp b/esphome/components/esp32_dac/esp32_dac.cpp index 20a047f0ba..7f37e2ce47 100644 --- a/esphome/components/esp32_dac/esp32_dac.cpp +++ b/esphome/components/esp32_dac/esp32_dac.cpp @@ -2,9 +2,14 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 +#ifdef USE_ARDUINO #include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { namespace esp32_dac { @@ -15,6 +20,11 @@ void ESP32DAC::setup() { ESP_LOGCONFIG(TAG, "Setting up ESP32 DAC Output..."); this->pin_->setup(); this->turn_off(); + +#ifdef USE_ESP_IDF + auto channel = pin_->get_pin() == 25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2; + dac_output_enable(channel); +#endif } void ESP32DAC::dump_config() { @@ -28,7 +38,14 @@ void ESP32DAC::write_state(float state) { state = 1.0f - state; state = state * 255; + +#ifdef USE_ESP_IDF + auto channel = pin_->get_pin() == 25 ? DAC_CHANNEL_1 : DAC_CHANNEL_2; + dac_output_voltage(channel, (uint8_t) state); +#endif +#ifdef USE_ARDUINO dacWrite(this->pin_->get_pin(), state); +#endif } } // namespace esp32_dac diff --git a/esphome/components/esp32_dac/esp32_dac.h b/esphome/components/esp32_dac/esp32_dac.h index 648efcfe4f..0fb1ddebf0 100644 --- a/esphome/components/esp32_dac/esp32_dac.h +++ b/esphome/components/esp32_dac/esp32_dac.h @@ -1,18 +1,18 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_dac { class ESP32DAC : public output::FloatOutput, public Component { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } /// Initialize pin void setup() override; @@ -23,7 +23,7 @@ class ESP32DAC : public output::FloatOutput, public Component { protected: void write_state(float state) override; - GPIOPin *pin_; + InternalGPIOPin *pin_; }; } // namespace esp32_dac diff --git a/esphome/components/esp32_dac/output.py b/esphome/components/esp32_dac/output.py index 8534a1bae1..f119198618 100644 --- a/esphome/components/esp32_dac/output.py +++ b/esphome/components/esp32_dac/output.py @@ -2,9 +2,9 @@ from esphome import pins from esphome.components import output import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] def valid_dac_pin(value): diff --git a/esphome/components/esp32_hall/esp32_hall.cpp b/esphome/components/esp32_hall/esp32_hall.cpp index 4bbf65e048..762497aedc 100644 --- a/esphome/components/esp32_hall/esp32_hall.cpp +++ b/esphome/components/esp32_hall/esp32_hall.cpp @@ -1,8 +1,8 @@ +#ifdef USE_ESP32 #include "esp32_hall.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" +#include namespace esphome { namespace esp32_hall { @@ -10,7 +10,9 @@ namespace esp32_hall { static const char *const TAG = "esp32_hall"; void ESP32HallSensor::update() { - float value = (hallRead() / 4095.0f) * 10000.0f; + adc1_config_width(ADC_WIDTH_BIT_12); + int value_int = hall_sensor_read(); + float value = (value_int / 4095.0f) * 10000.0f; ESP_LOGD(TAG, "'%s': Got reading %.0f µT", this->name_.c_str(), value); this->publish_state(value); } diff --git a/esphome/components/esp32_hall/esp32_hall.h b/esphome/components/esp32_hall/esp32_hall.h index 040280fff3..8db50c4667 100644 --- a/esphome/components/esp32_hall/esp32_hall.h +++ b/esphome/components/esp32_hall/esp32_hall.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_hall { diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index 4ba79de714..a752da2c97 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -3,13 +3,12 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, - ESP_PLATFORM_ESP32, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, ICON_MAGNET, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] esp32_hall_ns = cg.esphome_ns.namespace("esp32_hall") ESP32HallSensor = esp32_hall_ns.class_( diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 4c337055cb..0b0214c63e 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,14 +1,13 @@ 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_ID AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] -DEPENDENCIES = ["wifi"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["wifi", "esp32"] CONF_AUTHORIZED_DURATION = "authorized_duration" CONF_AUTHORIZER = "authorizer" diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index b584d2e8b9..fc58fbd264 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -5,7 +5,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_improv { diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 262124f983..af39ae4748 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -10,7 +10,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace esp32_improv { diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index 1564476ecf..cdf6aa3abd 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -9,12 +9,11 @@ from esphome.const import ( CONF_SETUP_MODE, CONF_SLEEP_DURATION, CONF_VOLTAGE_ATTENUATION, - ESP_PLATFORM_ESP32, ) from esphome.core import TimePeriod AUTO_LOAD = ["binary_sensor"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] esp32_touch_ns = cg.esphome_ns.namespace("esp32_touch") ESP32TouchComponent = esp32_touch_ns.class_("ESP32TouchComponent", cg.Component) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index 198a43a738..93640334cd 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -5,14 +5,12 @@ from esphome.const import ( CONF_NAME, CONF_PIN, CONF_THRESHOLD, - ESP_PLATFORM_ESP32, CONF_ID, ) -from esphome.pins import validate_gpio_pin +from esphome.components.esp32 import gpio from . import esp32_touch_ns, ESP32TouchComponent -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] -DEPENDENCIES = ["esp32_touch"] +DEPENDENCIES = ["esp32_touch", "esp32"] CONF_ESP32_TOUCH_ID = "esp32_touch_id" CONF_WAKEUP_THRESHOLD = "wakeup_threshold" @@ -32,7 +30,7 @@ TOUCH_PADS = { def validate_touch_pad(value): - value = validate_gpio_pin(value) + value = gpio.validate_gpio_pin(value) if value not in TOUCH_PADS: raise cv.Invalid(f"Pin {value} does not support touch pads.") return value diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 9ae671a4a9..801106ab6c 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -1,7 +1,8 @@ +#ifdef USE_ESP32 + #include "esp32_touch.h" #include "esphome/core/log.h" - -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/hal.h" namespace esphome { namespace esp32_touch { diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 8b92a482c0..c584a6d9bc 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -1,9 +1,16 @@ #pragma once +#ifdef USE_ESP32 + #include "esphome/core/component.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include -#ifdef ARDUINO_ARCH_ESP32 +#if ESP_IDF_VERSION_MAJOR >= 4 +#include +#else +#include +#endif namespace esphome { namespace esp32_touch { diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py new file mode 100644 index 0000000000..592de06440 --- /dev/null +++ b/esphome/components/esp8266/__init__.py @@ -0,0 +1,213 @@ +import logging + +from esphome.const import ( + CONF_BOARD, + CONF_BOARD_FLASH_MODE, + CONF_FRAMEWORK, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266 +from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS + +# force import gpio to register pin schema +from .gpio import esp8266_pin_to_code # noqa + + +CODEOWNERS = ["@esphome/core"] +_LOGGER = logging.getLogger(__name__) + + +def set_core_data(config): + CORE.data[KEY_ESP8266] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION_HINT] + ) + CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] + return config + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/esp8266/Arduino/releases) version to + # a PIO platformio/framework-arduinoespressif8266 value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 + if ver <= cv.Version(2, 4, 1): + return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if ver <= cv.Version(2, 6, 2): + return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * New framework historically have had some regressions, especially for WiFi. +# The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# The default/recommended arduino framework version +# - https://github.com/esp8266/Arduino/releases +# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) +# The platformio/espressif8266 version to use for arduino 2 framework versions +# - https://github.com/platformio/platform-espressif8266/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 +ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) +# for arduino 3 framework versions +ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)), + "latest": ("", cv.Version(3, 0, 2)), + "recommended": ( + _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), + RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, + ), + } + ver_value = value[CONF_VERSION] + default_ver_hint = None + if ver_value.lower() in lookups: + default_ver_hint = str(lookups[ver_value.lower()][1]) + ver_value = lookups[ver_value.lower()][0] + else: + with cv.suppress_invalid(): + ver = cv.Version.parse(cv.version_number(value)) + if ver <= cv.Version(2, 4, 1): + ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + elif ver <= cv.Version(2, 6, 2): + ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + else: + ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + default_ver_hint = str(ver) + + value[CONF_VERSION] = ver_value + + if CONF_VERSION_HINT not in value and default_ver_hint is None: + raise cv.Invalid("Needs a version hint to understand the framework version") + + ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) + value[CONF_VERSION_HINT] = ver_hint_s + plat_ver = value.get(CONF_PLATFORM_VERSION) + + if plat_ver is None: + ver_hint = cv.Version.parse(ver_hint_s) + if ver_hint >= cv.Version(3, 0, 0): + plat_ver = ARDUINO_3_PLATFORM_VERSION + elif ver_hint >= cv.Version(2, 5, 0): + plat_ver = ARDUINO_2_PLATFORM_VERSION + else: + plat_ver = cv.Version(1, 8, 0) + value[CONF_PLATFORM_VERSION] = str(plat_ver) + + if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected arduino framework version is not the recommended one" + ) + _LOGGER.warning( + "If there are connectivity or build issues please remove the manual version" + ) + + return value + + +CONF_VERSION_HINT = "version_hint" +CONF_PLATFORM_VERSION = "platform_version" +ARDUINO_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + } + ), + _arduino_check_versions, +) + + +BUILD_FLASH_MODES = ["qio", "qout", "dio", "dout"] +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, + cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean, + cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( + *BUILD_FLASH_MODES, lower=True + ), + } + ), + set_core_data, +) + + +async def to_code(config): + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_ESP8266") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + + conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") + cg.add_platformio_option( + "platform_packages", + [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"], + ) + cg.add_platformio_option( + "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" + ) + + # Default for platformio is LWIP2_LOW_MEMORY with: + # - MSS=536 + # - LWIP_FEATURES enabled + # - this only adds some optional features like IP incoming packet reassembly and NAPT + # see also: + # https://github.com/esp8266/Arduino/blob/master/tools/sdk/lwip2/include/lwipopts.h + + # Instead we use LWIP2_HIGHER_BANDWIDTH_LOW_FLASH with: + # - MSS=1460 + # - LWIP_FEATURES disabled (because we don't need them) + # Other projects like Tasmota & ESPEasy also use this + cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") + + if config[CONF_RESTORE_FROM_FLASH]: + cg.add_define("USE_ESP8266_PREFERENCES_FLASH") + + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when + # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make + # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of + # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, + # which always aborts if exceptions are disabled. + # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` + cg.add_build_flag("-DNEW_OOM_ABORT") + + cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE]) + + if config[CONF_BOARD] in ESP8266_FLASH_SIZES: + flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]] + ld_scripts = ESP8266_LD_SCRIPTS[flash_size] + ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + + if ver <= cv.Version(2, 3, 0): + # No ld script support + ld_script = None + if ver <= cv.Version(2, 4, 2): + # Old ld script path + ld_script = ld_scripts[0] + else: + ld_script = ld_scripts[1] + + if ld_script is not None: + cg.add_platformio_option("board_build.ldscript", ld_script) diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py new file mode 100644 index 0000000000..c49aae4ffa --- /dev/null +++ b/esphome/components/esp8266/boards.py @@ -0,0 +1,266 @@ +FLASH_SIZE_1_MB = 2 ** 20 +FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 +FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB +FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB +FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB + +ESP8266_FLASH_SIZES = { + "d1": FLASH_SIZE_4_MB, + "d1_mini": FLASH_SIZE_4_MB, + "d1_mini_lite": FLASH_SIZE_1_MB, + "d1_mini_pro": FLASH_SIZE_16_MB, + "esp01": FLASH_SIZE_512_KB, + "esp01_1m": FLASH_SIZE_1_MB, + "esp07": FLASH_SIZE_4_MB, + "esp12e": FLASH_SIZE_4_MB, + "esp210": FLASH_SIZE_4_MB, + "esp8285": FLASH_SIZE_1_MB, + "esp_wroom_02": FLASH_SIZE_2_MB, + "espduino": FLASH_SIZE_4_MB, + "espectro": FLASH_SIZE_4_MB, + "espino": FLASH_SIZE_4_MB, + "espinotee": FLASH_SIZE_4_MB, + "espmxdevkit": FLASH_SIZE_1_MB, + "espresso_lite_v1": FLASH_SIZE_4_MB, + "espresso_lite_v2": FLASH_SIZE_4_MB, + "gen4iod": FLASH_SIZE_512_KB, + "heltec_wifi_kit_8": FLASH_SIZE_4_MB, + "huzzah": FLASH_SIZE_4_MB, + "inventone": FLASH_SIZE_4_MB, + "modwifi": FLASH_SIZE_2_MB, + "nodemcu": FLASH_SIZE_4_MB, + "nodemcuv2": FLASH_SIZE_4_MB, + "oak": FLASH_SIZE_4_MB, + "phoenix_v1": FLASH_SIZE_4_MB, + "phoenix_v2": FLASH_SIZE_4_MB, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_MB, + "sparkfunBlynk": FLASH_SIZE_4_MB, + "thing": FLASH_SIZE_512_KB, + "thingdev": FLASH_SIZE_512_KB, + "wifi_slot": FLASH_SIZE_1_MB, + "wifiduino": FLASH_SIZE_4_MB, + "wifinfo": FLASH_SIZE_1_MB, + "wio_link": FLASH_SIZE_4_MB, + "wio_node": FLASH_SIZE_4_MB, + "xinabox_cw01": FLASH_SIZE_4_MB, +} + +ESP8266_LD_SCRIPTS = { + FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), + FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), + FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), + FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), + FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), +} + +ESP8266_BASE_PINS = { + "A0": 17, + "SS": 15, + "MOSI": 13, + "MISO": 12, + "SCK": 14, + "SDA": 4, + "SCL": 5, + "RX": 3, + "TX": 1, +} + +ESP8266_BOARD_PINS = { + "d1": { + "D0": 3, + "D1": 1, + "D2": 16, + "D3": 5, + "D4": 4, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 0, + "D9": 2, + "D10": 15, + "D11": 13, + "D12": 14, + "D13": 14, + "D14": 4, + "D15": 5, + "LED": 2, + }, + "d1_mini": { + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "LED": 2, + }, + "d1_mini_lite": "d1_mini", + "d1_mini_pro": "d1_mini", + "esp01": {}, + "esp01_1m": {}, + "esp07": {}, + "esp12e": {}, + "esp210": {}, + "esp8285": {}, + "esp_wroom_02": {}, + "espduino": {"LED": 16}, + "espectro": {"LED": 15, "BUTTON": 2}, + "espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0}, + "espinotee": {"LED": 16}, + "espmxdevkit": {}, + "espresso_lite_v1": {"LED": 16}, + "espresso_lite_v2": {"LED": 2}, + "gen4iod": {}, + "heltec_wifi_kit_8": "d1_mini", + "huzzah": { + "LED": 0, + "LED_RED": 0, + "LED_BLUE": 2, + "D4": 4, + "D5": 5, + "D12": 12, + "D13": 13, + "D14": 14, + "D15": 15, + "D16": 16, + }, + "inventone": {}, + "modwifi": {}, + "nodemcu": { + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 3, + "D10": 1, + "LED": 16, + }, + "nodemcuv2": "nodemcu", + "oak": { + "P0": 2, + "P1": 5, + "P2": 0, + "P3": 3, + "P4": 1, + "P5": 4, + "P6": 15, + "P7": 13, + "P8": 12, + "P9": 14, + "P10": 16, + "P11": 17, + "LED": 5, + }, + "phoenix_v1": {"LED": 16}, + "phoenix_v2": {"LED": 2}, + "sonoff_basic": {}, + "sonoff_s20": {}, + "sonoff_sv": {}, + "sonoff_th": {}, + "sparkfunBlynk": "thing", + "thing": {"LED": 5, "SDA": 2, "SCL": 14}, + "thingdev": "thing", + "wifi_slot": {"LED": 2}, + "wifiduino": { + "D0": 3, + "D1": 1, + "D2": 2, + "D3": 0, + "D4": 4, + "D5": 5, + "D6": 16, + "D7": 14, + "D8": 12, + "D9": 13, + "D10": 15, + "D11": 13, + "D12": 12, + "D13": 14, + }, + "wifinfo": { + "LED": 12, + "D0": 16, + "D1": 5, + "D2": 4, + "D3": 0, + "D4": 2, + "D5": 14, + "D6": 12, + "D7": 13, + "D8": 15, + "D9": 3, + "D10": 1, + }, + "wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0}, + "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, + "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, +} + +FLASH_SIZE_1_MB = 2 ** 20 +FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 +FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB +FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB +FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB + +ESP8266_FLASH_SIZES = { + "d1": FLASH_SIZE_4_MB, + "d1_mini": FLASH_SIZE_4_MB, + "d1_mini_lite": FLASH_SIZE_1_MB, + "d1_mini_pro": FLASH_SIZE_16_MB, + "esp01": FLASH_SIZE_512_KB, + "esp01_1m": FLASH_SIZE_1_MB, + "esp07": FLASH_SIZE_4_MB, + "esp12e": FLASH_SIZE_4_MB, + "esp210": FLASH_SIZE_4_MB, + "esp8285": FLASH_SIZE_1_MB, + "esp_wroom_02": FLASH_SIZE_2_MB, + "espduino": FLASH_SIZE_4_MB, + "espectro": FLASH_SIZE_4_MB, + "espino": FLASH_SIZE_4_MB, + "espinotee": FLASH_SIZE_4_MB, + "espmxdevkit": FLASH_SIZE_1_MB, + "espresso_lite_v1": FLASH_SIZE_4_MB, + "espresso_lite_v2": FLASH_SIZE_4_MB, + "gen4iod": FLASH_SIZE_512_KB, + "heltec_wifi_kit_8": FLASH_SIZE_4_MB, + "huzzah": FLASH_SIZE_4_MB, + "inventone": FLASH_SIZE_4_MB, + "modwifi": FLASH_SIZE_2_MB, + "nodemcu": FLASH_SIZE_4_MB, + "nodemcuv2": FLASH_SIZE_4_MB, + "oak": FLASH_SIZE_4_MB, + "phoenix_v1": FLASH_SIZE_4_MB, + "phoenix_v2": FLASH_SIZE_4_MB, + "sonoff_basic": FLASH_SIZE_1_MB, + "sonoff_s20": FLASH_SIZE_1_MB, + "sonoff_sv": FLASH_SIZE_1_MB, + "sonoff_th": FLASH_SIZE_1_MB, + "sparkfunBlynk": FLASH_SIZE_4_MB, + "thing": FLASH_SIZE_512_KB, + "thingdev": FLASH_SIZE_512_KB, + "wifi_slot": FLASH_SIZE_1_MB, + "wifiduino": FLASH_SIZE_4_MB, + "wifinfo": FLASH_SIZE_1_MB, + "wio_link": FLASH_SIZE_4_MB, + "wio_node": FLASH_SIZE_4_MB, + "xinabox_cw01": FLASH_SIZE_4_MB, +} + +ESP8266_LD_SCRIPTS = { + FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), + FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), + FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), + FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), + FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), +} diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py new file mode 100644 index 0000000000..16a050360c --- /dev/null +++ b/esphome/components/esp8266/const.py @@ -0,0 +1,8 @@ +import esphome.codegen as cg + +KEY_ESP8266 = "esp8266" +KEY_BOARD = "board" +CONF_RESTORE_FROM_FLASH = "restore_from_flash" + +# esp8266 namespace is already defined by arduino, manually prefix esphome +esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp new file mode 100644 index 0000000000..453f772e09 --- /dev/null +++ b/esphome/components/esp8266/core.cpp @@ -0,0 +1,37 @@ +#ifdef USE_ESP8266 + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" +#include +#include + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::yield(); } +uint32_t IRAM_ATTR HOT millis() { return ::millis(); } +void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } +uint32_t IRAM_ATTR HOT micros() { return ::micros(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } +void arch_restart() { + ESP.restart(); // NOLINT(readability-static-accessed-through-instance) + // restart() doesn't always end execution + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void IRAM_ATTR HOT arch_feed_wdt() { + ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) +} + +uint8_t progmem_read_byte(const uint8_t *addr) { + return pgm_read_byte(addr); // NOLINT +} +uint32_t arch_get_cpu_cycle_count() { + return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) +} +uint32_t arch_get_cpu_freq_hz() { return F_CPU; } + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp new file mode 100644 index 0000000000..4dbffa7f6c --- /dev/null +++ b/esphome/components/esp8266/gpio.cpp @@ -0,0 +1,96 @@ +#ifdef USE_ESP8266 + +#include "gpio.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace esp8266 { + +static const char *const TAG = "esp8266"; + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { + uint8_t arduino_mode = 0; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? ONHIGH : ONLOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? ONLOW : ONHIGH; + break; + } + + attachInterruptArg(pin_, func, arg, arduino_mode); +} +void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { + uint8_t mode; + if (flags == gpio::FLAG_INPUT) { + mode = INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + mode = OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + mode = INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + mode = INPUT_PULLDOWN_16; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + mode = OUTPUT_OPEN_DRAIN; + } else { + return; + } + pinMode(pin_, mode); // NOLINT +} + +std::string ESP8266GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool ESP8266GPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void ESP8266GPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } + +} // namespace esp8266 + +using namespace esp8266; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); +} + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h new file mode 100644 index 0000000000..465d1099ae --- /dev/null +++ b/esphome/components/esp8266/gpio.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace esp8266 { + +class ESP8266GPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py new file mode 100644 index 0000000000..0ebfbd6f69 --- /dev/null +++ b/esphome/components/esp8266/gpio.py @@ -0,0 +1,170 @@ +import logging + +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome import pins +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from . import boards +from .const import KEY_BOARD, KEY_ESP8266, esp8266_ns + + +_LOGGER = logging.getLogger(__name__) + + +ESP8266GPIOPin = esp8266_ns.class_("ESP8266GPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_ESP8266][KEY_BOARD] + board_pins = boards.ESP8266_BOARD_PINS.get(board, {}) + + # Resolved aliased board pins (shorthand when two boards have the same pin configuration) + while isinstance(board_pins, str): + board_pins = boards.ESP8266_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.ESP8266_BASE_PINS: + return boards.ESP8266_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 17: + raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "ESP8266: Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + return value + + +def validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_open_drain = mode[CONF_OPEN_DRAIN] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + is_analog = mode[CONF_ANALOG] + + if (not is_analog) and num == 17: + raise cv.Invalid( + "GPIO17 (TOUT) is an analog-only pin on the ESP8266.", + [CONF_MODE], + ) + if is_analog and num != 17: + raise cv.Invalid( + "Only GPIO17 is analog-capable on ESP8266.", + [CONF_MODE, CONF_ANALOG], + ) + if is_open_drain and not is_output: + raise cv.Invalid( + "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] + ) + if is_pullup and num == 0: + raise cv.Invalid( + "GPIO Pin 0 does not support pullup pin mode. " + "Please choose another pin.", + [CONF_MODE, CONF_PULLUP], + ) + if is_pulldown and num != 16: + raise cv.Invalid("Only GPIO16 supports pulldown.", [CONF_MODE, CONF_PULLDOWN]) + + # (input, output, open_drain, pullup, pulldown) + supported_modes = { + # INPUT + (True, False, False, False, False), + # OUTPUT + (False, True, False, False, False), + # INPUT_PULLUP + (True, False, False, True, False), + # INPUT_PULLDOWN_16 + (True, False, False, False, True), + # OUTPUT_OPEN_DRAIN + (False, True, True, False, False), + } + key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown) + if key not in supported_modes: + raise cv.Invalid( + "This pin mode is not supported on ESP8266", + [CONF_MODE], + ) + + return value + + +CONF_ANALOG = "analog" +ESP8266_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(ESP8266GPIOPin), + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_ANALOG, default=False): cv.boolean, + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + }, + validate_supports, +) + + +@pins.PIN_SCHEMA_REGISTRY.register("esp8266", ESP8266_PIN_SCHEMA) +async def esp8266_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp new file mode 100644 index 0000000000..7a8fdb8289 --- /dev/null +++ b/esphome/components/esp8266/preferences.cpp @@ -0,0 +1,263 @@ +#ifdef USE_ESP8266 + +#include +extern "C" { +#include "spi_flash.h" +} + +#include "preferences.h" +#include +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/defines.h" + +namespace esphome { +namespace esp8266 { + +static const char *const TAG = "esp8266.preferences"; + +static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; +#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) +static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; +static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; + +#ifdef USE_ESP8266_PREFERENCES_FLASH +static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; +#else +static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; +#endif + +static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { + if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { + return false; + } + *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) + return true; +} + +static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { + if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { + return false; + } + if (index < 32 && s_prevent_write) { + return false; + } + + auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) + *ptr = value; + return true; +} + +extern "C" uint32_t _SPIFFS_end; // NOLINT + +static const uint32_t get_esp8266_flash_sector() { + union { + uint32_t *ptr; + uint32_t uint; + } data{}; + data.ptr = &_SPIFFS_end; + return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; +} +static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } + +template uint32_t calculate_crc(It first, It last, uint32_t type) { + uint32_t crc = type; + while (first != last) { + crc ^= (*first++ * 2654435769UL) >> 1; + } + return crc; +} + +static bool safe_flash() { + if (!s_flash_dirty) + return true; + + ESP_LOGVV(TAG, "Saving preferences to flash..."); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + return false; + } + + s_flash_dirty = false; + return true; +} + +static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) { + uint32_t j = offset + i; + if (j >= ESP8266_FLASH_STORAGE_SIZE) + return false; + uint32_t v = data[i]; + uint32_t *ptr = &s_flash_storage[j]; + if (*ptr != v) + s_flash_dirty = true; + *ptr = v; + } + return safe_flash(); +} + +static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { + for (size_t i = 0; i < len; i++) { + uint32_t j = offset + i; + if (j >= ESP8266_FLASH_STORAGE_SIZE) + return false; + data[i] = s_flash_storage[j]; + } + return true; +} + +static bool safe_to_rtc(size_t offset, const uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) + if (!esp_rtc_user_mem_write(offset + i, data[i])) + return false; + return true; +} + +static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { + for (uint32_t i = 0; i < len; i++) + if (!esp_rtc_user_mem_read(offset + i, &data[i])) + return false; + return true; +} + +class ESP8266PreferenceBackend : public ESPPreferenceBackend { + public: + size_t offset = 0; + uint32_t type = 0; + bool in_flash = false; + size_t length_words = 0; + + bool save(const uint8_t *data, size_t len) override { + if ((len + 3) / 4 != length_words) { + return false; + } + std::vector buffer; + buffer.resize(length_words + 1); + memcpy(buffer.data(), data, len); + buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); + + if (in_flash) { + return safe_to_flash(offset, buffer.data(), buffer.size()); + } else { + return safe_to_rtc(offset, buffer.data(), buffer.size()); + } + } + bool load(uint8_t *data, size_t len) override { + if ((len + 3) / 4 != length_words) { + return false; + } + std::vector buffer; + buffer.resize(length_words + 1); + bool ret; + if (in_flash) { + ret = load_from_flash(offset, buffer.data(), buffer.size()); + } else { + ret = load_from_rtc(offset, buffer.data(), buffer.size()); + } + if (!ret) + return false; + + uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); + return buffer[buffer.size() - 1] == crc; + } +}; + +class ESP8266Preferences : public ESPPreferences { + public: + uint32_t current_offset = 0; + uint32_t current_flash_offset = 0; // in words + + void setup() { + s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT + ESP_LOGVV(TAG, "Loading preferences from flash..."); + + { + InterruptLock lock; + spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + uint32_t length_words = (length + 3) / 4; + if (in_flash) { + uint32_t start = current_flash_offset; + uint32_t end = start + length_words + 1; + if (end > ESP8266_FLASH_STORAGE_SIZE) + return {}; + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = start; + pref->type = type; + pref->length_words = length_words; + pref->in_flash = true; + current_flash_offset = end; + return {pref}; + } + + uint32_t start = current_offset; + uint32_t end = start + length_words + 1; + bool in_normal = start < 96; + // Normal: offset 0-95 maps to RTC offset 32 - 127, + // Eboot: offset 96-127 maps to RTC offset 0 - 31 words + if (in_normal && end > 96) { + // start is in normal but end is not -> switch to Eboot + current_offset = start = 96; + end = start + length_words + 1; + in_normal = false; + } + + if (end > 128) { + // Doesn't fit in data, return uninitialized preference obj. + return {}; + } + + uint32_t rtc_offset = in_normal ? start + 32 : start - 96; + + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = rtc_offset; + pref->type = type; + pref->length_words = length_words; + pref->in_flash = false; + current_offset += length_words + 1; + return pref; + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { +#ifdef USE_ESP8266_PREFERENCES_FLASH + return make_preference(length, type, true); +#else + return make_preference(length, type, false); +#endif + } +}; + +void setup_preferences() { + auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + pref->setup(); + global_preferences = pref; +} +void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } + +} // namespace esp8266 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h new file mode 100644 index 0000000000..edec915794 --- /dev/null +++ b/esphome/components/esp8266/preferences.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_ESP8266 + +namespace esphome { +namespace esp8266 { + +void setup_preferences(); +void preferences_prevent_write(bool prevent); + +} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 186653acd1..e472edf2a7 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -1,11 +1,11 @@ -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "esp8266_pwm.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) #error ESP8266 PWM requires at least arduino_version 2.4.0 #endif diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.h b/esphome/components/esp8266_pwm/esp8266_pwm.h index 1bf875dc43..79530aacd4 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.h +++ b/esphome/components/esp8266_pwm/esp8266_pwm.h @@ -1,9 +1,9 @@ #pragma once -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" @@ -12,7 +12,7 @@ namespace esp8266_pwm { class ESP8266PWM : public output::FloatOutput, public Component { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_frequency(float frequency) { this->frequency_ = frequency; } /// Dynamically update frequency void update_frequency(float frequency) override { @@ -29,7 +29,7 @@ class ESP8266PWM : public output::FloatOutput, public Component { protected: void write_state(float state) override; - GPIOPin *pin_; + InternalGPIOPin *pin_; float frequency_{1000.0}; /// Cache last output level for dynamic frequency updating float last_output_{0.0}; diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 770d400013..3d52e5af16 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -7,10 +7,9 @@ from esphome.const import ( CONF_ID, CONF_NUMBER, CONF_PIN, - ESP_PLATFORM_ESP8266, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP8266] +DEPENDENCIES = ["esp8266"] def valid_pwm_pin(value): diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 95b7e40151..384fb3dbfb 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,7 +1,6 @@ from esphome import pins import esphome.config_validation as cv import esphome.codegen as cg -from esphome.components.network import add_mdns_library from esphome.const import ( CONF_DOMAIN, CONF_ID, @@ -9,17 +8,16 @@ from esphome.const import ( CONF_STATIC_IP, CONF_TYPE, CONF_USE_ADDRESS, - ESP_PLATFORM_ESP32, - CONF_ENABLE_MDNS, CONF_GATEWAY, CONF_SUBNET, CONF_DNS1, CONF_DNS2, ) from esphome.core import CORE, coroutine_with_priority +from esphome.components.network import IPAddress CONFLICTS_WITH = ["wifi"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] ethernet_ns = cg.esphome_ns.namespace("ethernet") @@ -55,7 +53,6 @@ MANUAL_IP_SCHEMA = cv.Schema( ) EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component) -IPAddress = cg.global_ns.class_("IPAddress") ManualIP = ethernet_ns.struct("ManualIP") @@ -74,20 +71,24 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(EthernetComponent), cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), - cv.Required(CONF_MDC_PIN): pins.output_pin, - cv.Required(CONF_MDIO_PIN): pins.input_output_pin, + cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( CLK_MODES, upper=True, space="_" ), cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, - cv.Optional(CONF_ENABLE_MDNS, default=True): cv.boolean, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, + cv.Optional("enable_mdns"): cv.invalid( + "This option has been removed. Please use the [disabled] option under the " + "new mdns component instead." + ), } ).extend(cv.COMPONENT_SCHEMA), _validate, + cv.only_with_arduino, ) @@ -122,6 +123,3 @@ async def to_code(config): cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) cg.add_define("USE_ETHERNET") - - if config[CONF_ENABLE_MDNS]: - add_mdns_library() diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index cad76ae476..d03211deaf 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -3,7 +3,7 @@ #include "esphome/core/util.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include @@ -75,10 +75,6 @@ void EthernetComponent::setup() { ESPHL_ERROR_CHECK(err, "ETH init error"); err = esp_eth_enable(); ESPHL_ERROR_CHECK(err, "ETH enable error"); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif } void EthernetComponent::loop() { const uint32_t now = millis(); @@ -118,8 +114,6 @@ void EthernetComponent::loop() { } break; } - - network_tick_mdns(); } void EthernetComponent::dump_config() { ESP_LOGCONFIG(TAG, "Ethernet:"); @@ -131,10 +125,10 @@ void EthernetComponent::dump_config() { } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { +network::IPAddress EthernetComponent::get_ip_address() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { @@ -229,10 +223,10 @@ bool EthernetComponent::is_connected() { return this->state_ == EthernetComponen void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - ESP_LOGCONFIG(TAG, " IP Address: %s", IPAddress(ip.ip.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(ip.ip.addr).str().c_str()); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - ESP_LOGCONFIG(TAG, " Subnet: %s", IPAddress(ip.netmask.addr).toString().c_str()); - ESP_LOGCONFIG(TAG, " Gateway: %s", IPAddress(ip.gw.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(ip.netmask.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(ip.gw.addr).str().c_str()); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 4) const ip_addr_t *dns_ip1 = dns_getserver(0); @@ -243,8 +237,8 @@ void EthernetComponent::dump_connect_params_() { ip_addr_t tmp_ip2 = dns_getserver(1); const ip_addr_t *dns_ip2 = &tmp_ip2; #endif - ESP_LOGCONFIG(TAG, " DNS1: %s", IPAddress(dns_ip1->u_addr.ip4.addr).toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", IPAddress(dns_ip2->u_addr.ip4.addr).toString().c_str()); + ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); + ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str()); uint8_t mac[6]; esp_eth_get_mac(mac); ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -270,4 +264,4 @@ void EthernetComponent::set_use_address(const std::string &use_address) { this-> } // namespace ethernet } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index dc8816ef86..3e0c798a0c 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -1,9 +1,10 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/network/ip_address.h" #include "esp_eth.h" #include @@ -19,11 +20,11 @@ enum EthernetType { }; struct ManualIP { - IPAddress static_ip; - IPAddress gateway; - IPAddress subnet; - IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. - IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. + network::IPAddress static_ip; + network::IPAddress gateway; + network::IPAddress subnet; + network::IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. + network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; enum class EthernetComponentState { @@ -50,7 +51,7 @@ class EthernetComponent : public Component { void set_clk_mode(eth_clock_mode_t clk_mode); void set_manual_ip(const ManualIP &manual_ip); - IPAddress get_ip_address(); + network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); @@ -84,4 +85,4 @@ extern EthernetComponent *global_eth_component; } // namespace ethernet } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/exposure_notifications/exposure_notifications.cpp b/esphome/components/exposure_notifications/exposure_notifications.cpp index 9db181fbee..3083cf429c 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.cpp +++ b/esphome/components/exposure_notifications/exposure_notifications.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace exposure_notifications { diff --git a/esphome/components/exposure_notifications/exposure_notifications.h b/esphome/components/exposure_notifications/exposure_notifications.h index 6b9f61b2a0..f7383c28d9 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.h +++ b/esphome/components/exposure_notifications/exposure_notifications.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace exposure_notifications { diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 90b1e4ace9..81597f3466 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -1,5 +1,6 @@ #include "ezo.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ezo { @@ -24,7 +25,7 @@ void EZOSensor::update() { return; } uint8_t c = 'R'; - this->write_bytes_raw(&c, 1); + this->write(&c, 1); this->state_ |= EZO_STATE_WAIT; this->start_time_ = millis(); this->wait_time_ = 900; @@ -35,7 +36,7 @@ void EZOSensor::loop() { if (!(this->state_ & EZO_STATE_WAIT)) { if (this->state_ & EZO_STATE_SEND_TEMP) { int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_); - this->write_bytes_raw(buf, len); + this->write(buf, len); this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP; this->start_time_ = millis(); this->wait_time_ = 300; diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index a57115beb4..921b8d57e7 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -27,7 +27,7 @@ struct FanStateRTCState { }; void FanState::setup() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); FanStateRTCState recovered{}; if (!this->rtc_.load(&recovered)) return; diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index 15a5c8984c..486364d0c0 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "fastled_light.h" #include "esphome/core/log.h" @@ -37,3 +39,5 @@ void FastLEDLightOutput::write_state(light::LightState *state) { } // namespace fastled_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index d1c470599d..80840c3003 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/light/addressable_light.h" @@ -237,3 +239,5 @@ class FastLEDLightOutput : public light::AddressableLight { } // namespace fastled_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index d437d01dcf..acf9488ae3 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -45,10 +45,11 @@ CONFIG_SCHEMA = cv.All( fastled_base.BASE_SCHEMA.extend( { cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_PIN): pins.output_pin, + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, } ), _validate, + cv.only_with_arduino, ) diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index d6ba0e8358..a729fc015a 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -24,13 +24,16 @@ CHIPSETS = [ "DOTSTAR", ] -CONFIG_SCHEMA = fastled_base.BASE_SCHEMA.extend( - { - cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), - cv.Required(CONF_DATA_PIN): pins.output_pin, - cv.Required(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_RATE): cv.frequency, - } +CONFIG_SCHEMA = cv.All( + fastled_base.BASE_SCHEMA.extend( + { + cv.Required(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), + cv.Required(CONF_DATA_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_DATA_RATE): cv.frequency, + } + ), + cv.only_with_arduino, ) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index b0c0be59af..be17e29de3 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -15,7 +15,7 @@ void FingerprintGrowComponent::update() { } if (this->sensing_pin_ != nullptr) { - if (this->sensing_pin_->digital_read() == HIGH) { + if (this->sensing_pin_->digital_read()) { ESP_LOGV(TAG, "No touch sensing"); this->waiting_removal_ = false; return; diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 9039d0d62e..97a7ba3d54 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -14,6 +14,7 @@ from esphome.core import coroutine_with_priority CODEOWNERS = ["@esphome/core"] globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) +RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) MULTI_CONF = True @@ -32,22 +33,25 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): type_ = cg.RawExpression(config[CONF_TYPE]) template_args = cg.TemplateArguments(type_) - res_type = GlobalsComponent.template(template_args) + restore = config[CONF_RESTORE_VALUE] + + type = RestoringGlobalsComponent if restore else GlobalsComponent + res_type = type.template(template_args) initial_value = None if CONF_INITIAL_VALUE in config: initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) - rhs = GlobalsComponent.new(template_args, initial_value) + rhs = type.new(template_args, initial_value) glob = cg.Pvariable(config[CONF_ID], rhs, res_type) await cg.register_component(glob, config) - if config[CONF_RESTORE_VALUE]: + if restore: value = config[CONF_ID].id if isinstance(value, str): value = value.encode() hash_ = int(hashlib.md5(value).hexdigest()[:8], 16) - cg.add(glob.set_restore_value(hash_)) + cg.add(glob.set_name_hash(hash_)) @automation.register_action( diff --git a/esphome/components/globals/globals_component.h b/esphome/components/globals/globals_component.h index 397c55f6c4..b39c5f404b 100644 --- a/esphome/components/globals/globals_component.h +++ b/esphome/components/globals/globals_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include namespace esphome { namespace globals { @@ -16,37 +17,46 @@ template class GlobalsComponent : public Component { memcpy(this->value_, initial_value.data(), sizeof(T)); } + T &value() { return this->value_; } + void setup() override {} + + protected: + T value_{}; +}; + +template class RestoringGlobalsComponent : public Component { + public: + using value_type = T; + explicit RestoringGlobalsComponent() = default; + explicit RestoringGlobalsComponent(T initial_value) : value_(initial_value) {} + explicit RestoringGlobalsComponent( + std::array::type, std::extent::value> initial_value) { + memcpy(this->value_, initial_value.data(), sizeof(T)); + } + T &value() { return this->value_; } void setup() override { - if (this->restore_value_) { - this->rtc_ = global_preferences.make_preference(1944399030U ^ this->name_hash_); - this->rtc_.load(&this->value_); - } + this->rtc_ = global_preferences->make_preference(1944399030U ^ this->name_hash_); + this->rtc_.load(&this->value_); memcpy(&this->prev_value_, &this->value_, sizeof(T)); } float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override { - if (this->restore_value_) { - int diff = memcmp(&this->value_, &this->prev_value_, sizeof(T)); - if (diff != 0) { - this->rtc_.save(&this->value_); - memcpy(&this->prev_value_, &this->value_, sizeof(T)); - } + int diff = memcmp(&this->value_, &this->prev_value_, sizeof(T)); + if (diff != 0) { + this->rtc_.save(&this->value_); + memcpy(&this->prev_value_, &this->value_, sizeof(T)); } } - void set_restore_value(uint32_t name_hash) { - this->restore_value_ = true; - this->name_hash_ = name_hash; - } + void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; } protected: T value_{}; T prev_value_{}; - bool restore_value_{false}; uint32_t name_hash_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index f655207243..33a173fe2e 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/gpio/output/gpio_binary_output.h b/esphome/components/gpio/output/gpio_binary_output.h index 0a7dfb46e2..6b72c61c0f 100644 --- a/esphome/components/gpio/output/gpio_binary_output.h +++ b/esphome/components/gpio/output/gpio_binary_output.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/binary_output.h" namespace esphome { diff --git a/esphome/components/gpio/switch/gpio_switch.h b/esphome/components/gpio/switch/gpio_switch.h index b39e070c85..99f8060efa 100644 --- a/esphome/components/gpio/switch/gpio_switch.h +++ b/esphome/components/gpio/switch/gpio_switch.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/switch/switch.h" namespace esphome { diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 9d323e6e4d..e485373175 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -28,7 +28,7 @@ GPSListener = gps_ns.class_("GPSListener") CONF_GPS_ID = "gps_id" MULTI_CONF = True -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(GPS), @@ -64,7 +64,8 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("20s")) - .extend(uart.UART_DEVICE_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA), + cv.only_with_arduino, ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) diff --git a/esphome/components/gps/gps.cpp b/esphome/components/gps/gps.cpp index 1e8ca94e9e..8c924d629c 100644 --- a/esphome/components/gps/gps.cpp +++ b/esphome/components/gps/gps.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "gps.h" #include "esphome/core/log.h" @@ -69,3 +71,5 @@ void GPS::loop() { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/gps.h b/esphome/components/gps/gps.h index 50dd476ae3..40cda145ca 100644 --- a/esphome/components/gps/gps.h +++ b/esphome/components/gps/gps.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/uart/uart.h" #include "esphome/components/sensor/sensor.h" @@ -63,3 +65,5 @@ class GPS : public PollingComponent, public uart::UARTDevice { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/time/gps_time.cpp b/esphome/components/gps/time/gps_time.cpp index 5352c7e059..e46f24ba8e 100644 --- a/esphome/components/gps/time/gps_time.cpp +++ b/esphome/components/gps/time/gps_time.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "gps_time.h" #include "esphome/core/log.h" @@ -32,3 +34,5 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/gps/time/gps_time.h b/esphome/components/gps/time/gps_time.h index a1f69a7130..d0d1db83b5 100644 --- a/esphome/components/gps/time/gps_time.h +++ b/esphome/components/gps/time/gps_time.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/time/real_time_clock.h" #include "esphome/components/gps/gps.h" @@ -22,3 +24,5 @@ class GPSTime : public time::RealTimeClock, public GPSListener { } // namespace gps } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 6ede553fdb..ff736b7cb7 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -2,7 +2,7 @@ #include "esphome/components/display/display_buffer.h" #include "esphome/core/color.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include #include #include // std::cout, std::fixed @@ -34,12 +34,12 @@ void HistoryData::take_sample(float data) { this->count_ = (this->count_ + 1) % this->length_; ESP_LOGV(TAG, "Updating trace with value: %f", data); } - if (!isnan(data)) { + if (!std::isnan(data)) { // Recalc recent max/min this->recent_min_ = data; this->recent_max_ = data; for (int i = 0; i < this->length_; i++) { - if (!isnan(this->samples_[i])) { + if (!std::isnan(this->samples_[i])) { if (this->recent_max_ < this->samples_[i]) this->recent_max_ = this->samples_[i]; if (this->recent_min_ > this->samples_[i]) @@ -70,15 +70,15 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (auto *trace : traces_) { float mx = trace->get_tracedata()->get_recent_max(); float mn = trace->get_tracedata()->get_recent_min(); - if (isnan(ymax) || (ymax < mx)) + if (std::isnan(ymax) || (ymax < mx)) ymax = mx; - if (isnan(ymin) || (ymin > mn)) + if (std::isnan(ymin) || (ymin > mn)) ymin = mn; } // Adjust if manually overridden - if (!isnan(this->min_value_)) + if (!std::isnan(this->min_value_)) ymin = this->min_value_; - if (!isnan(this->max_value_)) + if (!std::isnan(this->max_value_)) ymax = this->max_value_; float yrange = ymax - ymin; @@ -89,20 +89,20 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo for (int16_t i = 0; i < this->width_; i++) { for (auto *trace : traces_) { float v = trace->get_tracedata()->get_value(i); - if (!isnan(v)) { + if (!std::isnan(v)) { if ((v - mn) > this->max_range_) break; if ((mx - v) > this->max_range_) break; - if (isnan(mx) || (v > mx)) + if (std::isnan(mx) || (v > mx)) mx = v; - if (isnan(mn) || (v < mn)) + if (std::isnan(mn) || (v < mn)) mn = v; } } } yrange = this->max_range_; - if (!isnan(mn)) { + if (!std::isnan(mn)) { ymin = mn; ymax = ymin + this->max_range_; } @@ -110,7 +110,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo } float y_per_div = this->min_range_; - if (!isnan(this->gridspacing_y_)) { + if (!std::isnan(this->gridspacing_y_)) { y_per_div = this->gridspacing_y_; } // Restrict drawing too many gridlines @@ -129,7 +129,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo yrange = ymax - ymin; /// Draw grid - if (!isnan(this->gridspacing_y_)) { + if (!std::isnan(this->gridspacing_y_)) { for (int y = yn; y <= ym; y++) { int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); for (int x = 0; x < this->width_; x += 2) { @@ -137,7 +137,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo } } } - if (!isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { + if (!std::isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) { int n = this->duration_ / this->gridspacing_x_; // Restrict drawing too many gridlines if (n > 20) { @@ -160,7 +160,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo uint16_t thick = trace->get_line_thickness(); for (int16_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; - if (!isnan(v) && (thick > 0)) { + if (!std::isnan(v) && (thick > 0)) { int16_t x = this->width_ - 1 - i; uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { diff --git a/esphome/components/hdc1080/hdc1080.cpp b/esphome/components/hdc1080/hdc1080.cpp index 507ac77a28..60e8943e67 100644 --- a/esphome/components/hdc1080/hdc1080.cpp +++ b/esphome/components/hdc1080/hdc1080.cpp @@ -1,5 +1,6 @@ #include "hdc1080.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace hdc1080 { @@ -36,18 +37,30 @@ void HDC1080Component::dump_config() { } void HDC1080Component::update() { uint16_t raw_temp; - if (!this->read_byte_16(HDC1080_CMD_TEMPERATURE, &raw_temp, 20)) { + if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(20); + if (this->read(reinterpret_cast(&raw_temp), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temp = i2c::i2ctohs(raw_temp); float temp = raw_temp * 0.0025177f - 40.0f; // raw * 2^-16 * 165 - 40 this->temperature_->publish_state(temp); uint16_t raw_humidity; - if (!this->read_byte_16(HDC1080_CMD_HUMIDITY, &raw_humidity, 20)) { + if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(20); + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); float humidity = raw_humidity * 0.001525879f; // raw * 2^-16 * 100 this->humidity_->publish_state(humidity); diff --git a/esphome/components/hlw8012/hlw8012.h b/esphome/components/hlw8012/hlw8012.h index 52fb03c020..5060957cf1 100644 --- a/esphome/components/hlw8012/hlw8012.h +++ b/esphome/components/hlw8012/hlw8012.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/pulse_counter/pulse_counter_sensor.h" @@ -31,8 +31,8 @@ class HLW8012Component : public PollingComponent { void set_current_resistor(float current_resistor) { current_resistor_ = current_resistor; } void set_voltage_divider(float voltage_divider) { voltage_divider_ = voltage_divider; } void set_sel_pin(GPIOPin *sel_pin) { sel_pin_ = sel_pin; } - void set_cf_pin(GPIOPin *cf_pin) { cf_pin_ = cf_pin; } - void set_cf1_pin(GPIOPin *cf1_pin) { cf1_pin_ = cf1_pin; } + void set_cf_pin(InternalGPIOPin *cf_pin) { cf_pin_ = cf_pin; } + void set_cf1_pin(InternalGPIOPin *cf1_pin) { cf1_pin_ = cf1_pin; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } @@ -48,9 +48,9 @@ class HLW8012Component : public PollingComponent { HLW8012SensorModels sensor_model_{HLW8012_SENSOR_MODEL_HLW8012}; uint64_t cf_total_pulses_{0}; GPIOPin *sel_pin_; - GPIOPin *cf_pin_; + InternalGPIOPin *cf_pin_; pulse_counter::PulseCounterStorage cf_store_; - GPIOPin *cf1_pin_; + InternalGPIOPin *cf1_pin_; pulse_counter::PulseCounterStorage cf1_store_; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 11e9c8e4d4..033cccc3d4 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -50,12 +50,8 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(HLW8012Component), cv.Required(CONF_SEL_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CF_PIN): cv.All( - pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_CF1_PIN): cv.All( - pins.internal_gpio_input_pullup_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_CF_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema), + cv.Required(CONF_CF1_PIN): cv.All(pins.internal_gpio_input_pullup_pin_schema), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h index f2573ff108..fb41b921d9 100644 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ b/esphome/components/hm3301/abstract_aqi_calculator.h @@ -1,5 +1,8 @@ #pragma once +#ifdef USE_ARDUINO +#include + namespace esphome { namespace hm3301 { @@ -10,3 +13,5 @@ class AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 627ee686fc..1410eac72b 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "abstract_aqi_calculator.h" namespace esphome { @@ -46,3 +48,5 @@ class AQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/hm3301/aqi_calculator_factory.h index 55608b6e51..3c6f9709b6 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/hm3301/aqi_calculator_factory.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "caqi_calculator.h" #include "aqi_calculator.h" @@ -27,3 +29,5 @@ class AQICalculatorFactory { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index 403bac2713..51158454d0 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "abstract_aqi_calculator.h" @@ -52,3 +54,5 @@ class CAQICalculator : public AbstractAQICalculator { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 96c1ec0ee9..759157f330 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "hm3301.h" @@ -102,3 +104,5 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index b024f93719..61bbf7e4ab 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" @@ -48,3 +50,5 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { } // namespace hm3301 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 2c3c2f7221..976a0488e1 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -84,6 +84,7 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x40)), _validate, + cv.only_with_arduino, ) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index eea2f0d407..a48b3c0acb 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,7 +2,6 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv -import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -12,7 +11,7 @@ from esphome.const import ( CONF_URL, CONF_ESP8266_DISABLE_SSL_SUPPORT, ) -from esphome.core import CORE, Lambda +from esphome.core import Lambda, CORE DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -67,35 +66,24 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, - cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( - cv.only_on_esp8266, cv.boolean - ), - } -).extend(cv.COMPONENT_SCHEMA) - - -def validate_framework(value): - if not CORE.is_esp8266: - # only for ESP8266 - return - - framework_version = fv.get_arduino_framework_version() - if framework_version is None or framework_version == "dev": - return - - if framework_version < "2.5.1": - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1, ", - "please check esphome->arduino_version", - ) - - -FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional( + CONF_TIMEOUT, default="5s" + ): cv.positive_time_period_milliseconds, + cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( + cv.only_on_esp8266, cv.boolean + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 5, 1), + esp32_arduino=cv.Version(0, 0, 0), + ), +) async def to_code(config): diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 3f5bf6da87..f88ee19e5c 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "http_request.h" #include "esphome/core/macros.h" #include "esphome/core/log.h" @@ -28,10 +30,10 @@ void HttpRequestComponent::set_url(std::string url) { void HttpRequestComponent::send(const std::vector &response_triggers) { bool begin_status = false; const String url = this->url_.c_str(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = this->client_.begin(url); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -79,7 +81,7 @@ void HttpRequestComponent::send(const std::vector ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d", this->url_.c_str(), http_code); } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::shared_ptr HttpRequestComponent::get_wifi_client_() { #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS if (this->secure_) { @@ -111,3 +113,5 @@ const char *HttpRequestComponent::get_string() { } // namespace http_request } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index fa29f8aa3a..511096e7fa 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/components/json/json_util.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" @@ -9,10 +11,10 @@ #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS #include @@ -54,7 +56,7 @@ class HttpRequestComponent : public Component { uint16_t timeout_{5000}; std::string body_; std::list
    headers_; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 std::shared_ptr wifi_client_; #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS std::shared_ptr wifi_client_secure_; @@ -137,3 +139,5 @@ class HttpRequestResponseTrigger : public Trigger { } // namespace http_request } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index a954b2ad59..b53284ae3f 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -1,5 +1,6 @@ #include "htu21d.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace htu21d { @@ -35,18 +36,30 @@ void HTU21DComponent::dump_config() { } void HTU21DComponent::update() { uint16_t raw_temperature; - if (!this->read_byte_16(HTU21D_REGISTER_TEMPERATURE, &raw_temperature, 50)) { + if (this->write(&HTU21D_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); float temperature = (float(raw_temperature & 0xFFFC)) * 175.72f / 65536.0f - 46.85f; uint16_t raw_humidity; - if (!this->read_byte_16(HTU21D_REGISTER_HUMIDITY, &raw_humidity, 50)) { + if (this->write(&HTU21D_REGISTER_HUMIDITY, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_humidity), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_humidity = i2c::i2ctohs(raw_humidity); float humidity = (float(raw_humidity & 0xFFFC)) * 125.0f / 65536.0f - 6.0f; ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 91c8317ee5..9fef649b03 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index f43729066d..46f0abacc6 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -2,30 +2,56 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( - CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, + CONF_INPUT, + CONF_OUTPUT, CONF_SCAN, CONF_SCL, CONF_SDA, CONF_ADDRESS, CONF_I2C_ID, - CONF_MULTIPLEXER, ) -from esphome.core import coroutine_with_priority +from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") -I2CComponent = i2c_ns.class_("I2CComponent", cg.Component) +I2CBus = i2c_ns.class_("I2CBus") +ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", I2CBus, cg.Component) +IDFI2CBus = i2c_ns.class_("IDFI2CBus", I2CBus, cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") -I2CMultiplexer = i2c_ns.class_("I2CMultiplexer", I2CDevice) + +CONF_SDA_PULLUP_ENABLED = "sda_pullup_enabled" +CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled" MULTI_CONF = True + + +def _bus_declare_type(value): + if CORE.using_arduino: + return cv.declare_id(ArduinoI2CBus)(value) + if CORE.using_esp_idf: + return cv.declare_id(IDFI2CBus)(value) + raise NotImplementedError + + +pin_with_input_and_output_support = cv.All( + pins.internal_gpio_pin_number({CONF_INPUT: True}), + pins.internal_gpio_pin_number({CONF_OUTPUT: True}), +) + + CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(): cv.declare_id(I2CComponent), - cv.Optional(CONF_SDA, default="SDA"): pins.input_pin, - cv.Optional(CONF_SCL, default="SCL"): pins.input_pin, + cv.GenerateID(): _bus_declare_type, + cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support, + cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All( + cv.only_with_esp_idf, cv.boolean + ), cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( cv.frequency, cv.Range(min=0, min_included=False) ), @@ -33,13 +59,6 @@ CONFIG_SCHEMA = cv.Schema( } ).extend(cv.COMPONENT_SCHEMA) -I2CMULTIPLEXER_SCHEMA = cv.Schema( - { - cv.Required(CONF_ID): cv.use_id(I2CMultiplexer), - cv.Required(CONF_CHANNEL): cv.uint8_t, - } -) - @coroutine_with_priority(1.0) async def to_code(config): @@ -48,10 +67,16 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_sda_pin(config[CONF_SDA])) + if CONF_SDA_PULLUP_ENABLED in config: + cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED])) cg.add(var.set_scl_pin(config[CONF_SCL])) + if CONF_SCL_PULLUP_ENABLED in config: + cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED])) + cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) cg.add(var.set_scan(config[CONF_SCAN])) - cg.add_library("Wire", None) + if CORE.using_arduino: + cg.add_library("Wire", None) def i2c_device_schema(default_address): @@ -62,8 +87,11 @@ def i2c_device_schema(default_address): :return: The i2c device schema, `extend` this in your config schema. """ schema = { - cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent), - cv.Optional(CONF_MULTIPLEXER): I2CMULTIPLEXER_SCHEMA, + cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus), + cv.Optional("multiplexer"): cv.invalid( + "This option has been removed, please see " + "the tca9584a docs for the updated way to use multiplexers" + ), } if default_address is None: schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address @@ -80,10 +108,5 @@ async def register_i2c_device(var, config): This is a coroutine, you need to await it with a 'yield' expression! """ parent = await cg.get_variable(config[CONF_I2C_ID]) - cg.add(var.set_i2c_parent(parent)) + cg.add(var.set_i2c_bus(parent)) cg.add(var.set_i2c_address(config[CONF_ADDRESS])) - if CONF_MULTIPLEXER in config: - multiplexer = await cg.get_variable(config[CONF_MULTIPLEXER][CONF_ID]) - cg.add( - var.set_i2c_multiplexer(multiplexer, config[CONF_MULTIPLEXER][CONF_CHANNEL]) - ) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index a3a3f7f82a..035c483344 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -1,303 +1,40 @@ #include "i2c.h" #include "esphome/core/log.h" -#include "esphome/core/helpers.h" -#include "esphome/core/application.h" +#include namespace esphome { namespace i2c { static const char *const TAG = "i2c"; -I2CComponent::I2CComponent() { -#ifdef ARDUINO_ARCH_ESP32 - static uint8_t next_i2c_bus_num = 0; - if (next_i2c_bus_num == 0) - this->wire_ = &Wire; - else - this->wire_ = new TwoWire(next_i2c_bus_num); // NOLINT(cppcoreguidelines-owning-memory) - next_i2c_bus_num++; -#else - this->wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) -#endif +bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { + // we have to copy in order to be able to change byte order + std::unique_ptr temp{new uint16_t[len]}; + for (size_t i = 0; i < len; i++) + temp[i] = htoi2cs(data[i]); + return write_register(a_register, reinterpret_cast(data), len * 2) == ERROR_OK; } -void I2CComponent::setup() { - this->wire_->begin(this->sda_pin_, this->scl_pin_); - this->wire_->setClock(this->frequency_); -} -void I2CComponent::dump_config() { - ESP_LOGCONFIG(TAG, "I2C Bus:"); - ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); - ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); - ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); - if (this->scan_) { - ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); - uint8_t found = 0; - for (uint8_t address = 1; address < 120; address++) { - this->wire_->beginTransmission(address); - uint8_t error = this->wire_->endTransmission(); - - if (error == 0) { - ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); - found++; - } else if (error == 4) { - ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); - } - - delay(1); - } - if (found == 0) { - ESP_LOGI(TAG, "Found no i2c devices!"); - } - } -} -float I2CComponent::get_setup_priority() const { return setup_priority::BUS; } - -void I2CComponent::raw_begin_transmission(uint8_t address) { - ESP_LOGVV(TAG, "Beginning Transmission to 0x%02X:", address); - this->wire_->beginTransmission(address); -} -bool I2CComponent::raw_end_transmission(uint8_t address, bool send_stop) { - uint8_t status = this->wire_->endTransmission(send_stop); - ESP_LOGVV(TAG, " Transmission ended. Status code: 0x%02X", status); - - switch (status) { - case 0: - break; - case 1: - ESP_LOGW(TAG, "Too much data to fit in transmitter buffer for address 0x%02X", address); - break; - case 2: - ESP_LOGW(TAG, "Received NACK on transmit of address 0x%02X", address); - break; - case 3: - ESP_LOGW(TAG, "Received NACK on transmit of data for address 0x%02X", address); - break; - default: - ESP_LOGW(TAG, "Unknown transmit error %u for address 0x%02X", status, address); - break; - } - - return status == 0; -} -bool I2CComponent::raw_request_from(uint8_t address, uint8_t len) { - ESP_LOGVV(TAG, "Requesting %u bytes from 0x%02X:", len, address); - uint8_t ret = this->wire_->requestFrom(address, len); - if (ret != len) { - ESP_LOGW(TAG, "Requesting %u bytes from 0x%02X failed!", len, address); - return false; - } - return true; -} -void HOT I2CComponent::raw_write(uint8_t address, const uint8_t *data, uint8_t len) { - for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); - this->wire_->write(data[i]); - App.feed_wdt(); - } -} -void HOT I2CComponent::raw_write_16(uint8_t address, const uint16_t *data, uint8_t len) { - for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Writing 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)", - BYTE_TO_BINARY(data[i] >> 8), BYTE_TO_BINARY(data[i]), data[i]); - this->wire_->write(data[i] >> 8); - this->wire_->write(data[i]); - App.feed_wdt(); - } -} - -bool I2CComponent::raw_receive(uint8_t address, uint8_t *data, uint8_t len) { - if (!this->raw_request_from(address, len)) - return false; - for (uint8_t i = 0; i < len; i++) { - data[i] = this->wire_->read(); - ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); - App.feed_wdt(); - } - return true; -} -bool I2CComponent::raw_receive_16(uint8_t address, uint16_t *data, uint8_t len) { - if (!this->raw_request_from(address, len * 2)) - return false; - auto *data_8 = reinterpret_cast(data); - for (uint8_t i = 0; i < len; i++) { - data_8[i * 2 + 1] = this->wire_->read(); - data_8[i * 2] = this->wire_->read(); - ESP_LOGVV(TAG, " Received 0b" BYTE_TO_BINARY_PATTERN BYTE_TO_BINARY_PATTERN " (0x%04X)", - BYTE_TO_BINARY(data_8[i * 2 + 1]), BYTE_TO_BINARY(data_8[i * 2]), data[i]); - } - return true; -} -bool I2CComponent::read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { - if (!this->write_bytes(address, a_register, nullptr, 0)) - return false; - - if (conversion > 0) - delay(conversion); - return this->raw_receive(address, data, len); -} -bool I2CComponent::read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len) { - return this->raw_receive(address, data, len); -} -bool I2CComponent::read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, - uint32_t conversion) { - if (!this->write_bytes(address, a_register, nullptr, 0)) - return false; - - if (conversion > 0) - delay(conversion); - return this->raw_receive_16(address, data, len); -} -bool I2CComponent::read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion) { - return this->read_bytes(address, a_register, data, 1, conversion); -} -bool I2CComponent::read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion) { - return this->read_bytes_16(address, a_register, data, 1, conversion); -} -bool I2CComponent::write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, &a_register, 1); - this->raw_write(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len) { - this->raw_begin_transmission(address); - this->raw_write(address, &a_register, 1); - this->raw_write_16(address, data, len); - return this->raw_end_transmission(address); -} -bool I2CComponent::write_byte(uint8_t address, uint8_t a_register, uint8_t data) { - return this->write_bytes(address, a_register, &data, 1); -} -bool I2CComponent::write_byte_16(uint8_t address, uint8_t a_register, uint16_t data) { - return this->write_bytes_16(address, a_register, &data, 1); -} - -void I2CDevice::set_i2c_address(uint8_t address) { this->address_ = address; } -#ifdef USE_I2C_MULTIPLEXER -void I2CDevice::set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel) { - ESP_LOGVV(TAG, " Setting Multiplexer %p for channel %d", multiplexer, channel); - this->multiplexer_ = multiplexer; - this->channel_ = channel; -} - -void I2CDevice::check_multiplexer_() { - if (this->multiplexer_ != nullptr) { - ESP_LOGVV(TAG, "Multiplexer setting channel to %d", this->channel_); - this->multiplexer_->set_channel(this->channel_); - } -} -#endif - -void I2CDevice::raw_begin_transmission() { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - this->parent_->raw_begin_transmission(this->address_); -} -bool I2CDevice::raw_end_transmission(bool send_stop) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->raw_end_transmission(this->address_, send_stop); -} -void I2CDevice::raw_write(const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - this->parent_->raw_write(this->address_, data, len); -} -bool I2CDevice::read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes(this->address_, a_register, data, len, conversion); -} -bool I2CDevice::read_bytes_raw(uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes_raw(this->address_, data, len); -} -bool I2CDevice::read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_byte(this->address_, a_register, data, conversion); -} -bool I2CDevice::write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes(this->address_, a_register, data, len); -} -bool I2CDevice::write_bytes_raw(const uint8_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes_raw(this->address_, data, len); -} -bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_byte(this->address_, a_register, data); -} -bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_bytes_16(this->address_, a_register, data, len, conversion); -} -bool I2CDevice::read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->read_byte_16(this->address_, a_register, data, conversion); -} -bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_bytes_16(this->address_, a_register, data, len); -} -bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT -#ifdef USE_I2C_MULTIPLEXER - this->check_multiplexer_(); -#endif - return this->parent_->write_byte_16(this->address_, a_register, data); -} -void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } - I2CRegister &I2CRegister::operator=(uint8_t value) { - this->parent_->write_byte(this->register_, value); + this->parent_->write_register(this->register_, &value, 1); return *this; } - I2CRegister &I2CRegister::operator&=(uint8_t value) { - this->parent_->write_byte(this->register_, this->get() & value); + value &= get(); + this->parent_->write_register(this->register_, &value, 1); return *this; } - I2CRegister &I2CRegister::operator|=(uint8_t value) { - this->parent_->write_byte(this->register_, this->get() | value); + value |= get(); + this->parent_->write_register(this->register_, &value, 1); return *this; } -uint8_t I2CRegister::get() { +uint8_t I2CRegister::get() const { uint8_t value = 0x00; - this->parent_->read_byte(this->register_, &value); + this->parent_->read_register(this->register_, &value, 1); return value; } -I2CRegister &I2CRegister::operator=(const std::vector &value) { - this->parent_->write_bytes(this->register_, value); - return *this; -} } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 6f93d4c0e4..71ab650e97 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,198 +1,79 @@ #pragma once -#include -#include "esphome/core/defines.h" -#include "esphome/core/component.h" -#include "esphome/core/helpers.h" +#include "i2c_bus.h" +#include "esphome/core/optional.h" +#include +#include namespace esphome { namespace i2c { #define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); -/** The I2CComponent is the base of ESPHome's i2c communication. - * - * It handles setting up the bus (with pins, clock frequency) and provides nice helper functions to - * make reading from the i2c bus easier (see read_bytes, write_bytes) and safe (with read timeouts). - * - * For the user, it has a few setters (see set_sda_pin, set_scl_pin, set_frequency) - * to setup some parameters for the bus. Additionally, the i2c component has a scan feature that will - * scan the entire 7-bit i2c address range for devices that respond to transmissions to make finding - * the address of an i2c device easier. - * - * On the ESP32, you can even have multiple I2C bus for communication, simply create multiple - * I2CComponents, each with different SDA and SCL pins and use `set_parent` on all I2CDevices that use - * the non-first I2C bus. - */ -class I2CComponent : public Component { - public: - I2CComponent(); - void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } - void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } - void set_frequency(uint32_t frequency) { frequency_ = frequency; } - void set_scan(bool scan) { scan_ = scan; } - - /** Read len amount of bytes from a register into data. Optionally with a conversion time after - * writing the register value to the bus. - * - * @param address The address to send the request to. - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 8-bit bytes into. - * @param len The amount of bytes to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - bool read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len); - - /** Read len amount of 16-bit words (MSB first) from a register into data. - * - * @param address The address to send the request to. - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 16-bit words into. - * @param len The amount of 16-bit words to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); - - /// Read a single byte from a register into the data variable. Return true if successful. - bool read_byte(uint8_t address, uint8_t a_register, uint8_t *data, uint32_t conversion = 0); - - /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. - bool read_byte_16(uint8_t address, uint8_t a_register, uint16_t *data, uint32_t conversion = 0); - - /** Write len amount of 8-bit bytes to the specified register for address. - * - * @param address The address to use for the transmission. - * @param a_register The register to write the values to. - * @param data An array from which len bytes of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len); - bool write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len); - - /** Write len amount of 16-bit words (MSB first) to the specified register for address. - * - * @param address The address to use for the transmission. - * @param a_register The register to write the values to. - * @param data An array from which len 16-bit words of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len); - - /// Write a single byte of data into the specified register of address. Return true if successful. - bool write_byte(uint8_t address, uint8_t a_register, uint8_t data); - - /// Write a single 16-bit word of data into the specified register of address. Return true if successful. - bool write_byte_16(uint8_t address, uint8_t a_register, uint16_t data); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Begin a write transmission to an address. - void raw_begin_transmission(uint8_t address); - - /// End a write transmission to an address, return true if successful. - bool raw_end_transmission(uint8_t address, bool send_stop = true); - - /** Request data from an address with a number of (8-bit) bytes. - * - * @param address The address to request the bytes from. - * @param len The number of bytes to receive, must not be 0. - * @return True if all requested bytes were read, false otherwise. - */ - bool raw_request_from(uint8_t address, uint8_t len); - - /// Write len amount of bytes from data to address. begin_transmission_ must be called before this. - void raw_write(uint8_t address, const uint8_t *data, uint8_t len); - - /// Write len amount of 16-bit words from data to address. begin_transmission_ must be called before this. - void raw_write_16(uint8_t address, const uint16_t *data, uint8_t len); - - /// Request len amount of bytes from address and write the result it into data. Returns true iff was successful. - bool raw_receive(uint8_t address, uint8_t *data, uint8_t len); - - /// Request len amount of 16-bit words from address and write the result into data. Returns true iff was successful. - bool raw_receive_16(uint8_t address, uint16_t *data, uint8_t len); - - /// Setup the i2c. bus - void setup() override; - void dump_config() override; - /// Set a very high setup priority to make sure it's loaded before all other hardware. - float get_setup_priority() const override; - - protected: - TwoWire *wire_; - uint8_t sda_pin_; - uint8_t scl_pin_; - uint32_t frequency_; - bool scan_; -}; - class I2CDevice; -class I2CMultiplexer; class I2CRegister { public: I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} I2CRegister &operator=(uint8_t value); - I2CRegister &operator=(const std::vector &value); I2CRegister &operator&=(uint8_t value); I2CRegister &operator|=(uint8_t value); - uint8_t get(); + explicit operator uint8_t() const { return get(); } + + uint8_t get() const; protected: I2CDevice *parent_; uint8_t register_; }; -/** All components doing communication on the I2C bus should subclass I2CDevice. - * - * This class stores 1. the address of the i2c device and has a helper function to allow - * users to manually set the address and 2. stores a reference to the "parent" I2CComponent. - * - * - * All this class basically does is to expose all helper functions from I2CComponent. - */ +// like ntohs/htons but without including networking headers. +// ("i2c" byte order is big-endian) +inline uint16_t i2ctohs(uint16_t i2cshort) { + union { + uint16_t x; + uint8_t y[2]; + } conv; + conv.x = i2cshort; + return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0); +} + +inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); } + class I2CDevice { public: I2CDevice() = default; - I2CDevice(I2CComponent *parent, uint8_t address) : address_(address), parent_(parent) {} - /// Manually set the i2c address of this device. - void set_i2c_address(uint8_t address); -#ifdef USE_I2C_MULTIPLEXER - /// Manually set the i2c multiplexer of this device. - void set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel); -#endif - /// Manually set the parent i2c bus for this device. - void set_i2c_parent(I2CComponent *parent); + void set_i2c_address(uint8_t address) { address_ = address; } + void set_i2c_bus(I2CBus *bus) { bus_ = bus; } I2CRegister reg(uint8_t a_register) { return {this, a_register}; } - /// Begin a write transmission. - void raw_begin_transmission(); + ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } + ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) { + ErrorCode err = this->write(&a_register, 1); + if (err != ERROR_OK) + return err; + return this->read(data, len); + } - /// End a write transmission, return true if successful. - bool raw_end_transmission(bool send_stop = true); + ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); } + ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) { + WriteBuffer buffers[2]; + buffers[0].data = &a_register; + buffers[0].len = 1; + buffers[1].data = data; + buffers[1].len = len; + return bus_->writev(address_, buffers, 2); + } - /// Write len amount of bytes from data. begin_transmission_ must be called before this. - void raw_write(const uint8_t *data, uint8_t len); + // Compat APIs - /** Read len amount of bytes from a register into data. Optionally with a conversion time after - * writing the register value to the bus. - * - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 8-bit bytes into. - * @param len The amount of bytes to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); - bool read_bytes_raw(uint8_t *data, uint8_t len); + bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len) { + return read_register(a_register, data, len) == ERROR_OK; + } + bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; } template optional> read_bytes(uint8_t a_register) { std::array res; @@ -209,18 +90,15 @@ class I2CDevice { return res; } - /** Read len amount of 16-bit words (MSB first) from a register into data. - * - * @param a_register The register number to write to the bus before reading. - * @param data An array to store len amount of 16-bit words into. - * @param len The amount of 16-bit words to request and write into data. - * @param conversion The time in ms between writing the register value and reading out the value. - * @return If the operation was successful. - */ - bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); + bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) { + if (read_register(a_register, reinterpret_cast(data), len * 2) != ERROR_OK) + return false; + for (size_t i = 0; i < len; i++) + data[i] = i2ctohs(data[i]); + return true; + } - /// Read a single byte from a register into the data variable. Return true if successful. - bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); + bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; } optional read_byte(uint8_t a_register) { uint8_t data; @@ -229,66 +107,30 @@ class I2CDevice { return data; } - /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. - bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); + bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); } - /** Write len amount of 8-bit bytes to the specified register. - * - * @param a_register The register to write the values to. - * @param data An array from which len bytes of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); - bool write_bytes_raw(const uint8_t *data, uint8_t len); - - /** Write a vector of data to a register. - * - * @param a_register The register to write to. - * @param data The data to write. - * @return If the operation was successful. - */ - bool write_bytes(uint8_t a_register, const std::vector &data) { - return this->write_bytes(a_register, data.data(), data.size()); + bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { + return write_register(a_register, data, len) == ERROR_OK; + } + + bool write_bytes(uint8_t a_register, const std::vector &data) { + return write_bytes(a_register, data.data(), data.size()); } - bool write_bytes_raw(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } template bool write_bytes(uint8_t a_register, const std::array &data) { - return this->write_bytes(a_register, data.data(), data.size()); - } - template bool write_bytes_raw(const std::array &data) { - return this->write_bytes_raw(data.data(), data.size()); + return write_bytes(a_register, data.data(), data.size()); } - /** Write len amount of 16-bit words (MSB first) to the specified register. - * - * @param a_register The register to write the values to. - * @param data An array from which len 16-bit words of data will be written to the bus. - * @param len The amount of bytes to write to the bus. - * @return If the operation was successful. - */ bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); - /// Write a single byte of data into the specified register. Return true if successful. - bool write_byte(uint8_t a_register, uint8_t data); + bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); } - /// Write a single 16-bit word of data into the specified register. Return true if successful. - bool write_byte_16(uint8_t a_register, uint16_t data); + bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } protected: - // Checks for multiplexer set and set channel - void check_multiplexer_(); uint8_t address_{0x00}; - I2CComponent *parent_{nullptr}; -#ifdef USE_I2C_MULTIPLEXER - I2CMultiplexer *multiplexer_{nullptr}; - uint8_t channel_; -#endif -}; -class I2CMultiplexer : public I2CDevice { - public: - I2CMultiplexer() = default; - virtual void set_channel(uint8_t channelno); + I2CBus *bus_{nullptr}; }; + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus.h b/esphome/components/i2c/i2c_bus.h new file mode 100644 index 0000000000..cb00260f43 --- /dev/null +++ b/esphome/components/i2c/i2c_bus.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include + +namespace esphome { +namespace i2c { + +enum ErrorCode { + ERROR_OK = 0, + ERROR_INVALID_ARGUMENT = 1, + ERROR_NOT_ACKNOWLEDGED = 2, + ERROR_TIMEOUT = 3, + ERROR_NOT_INITIALIZED = 4, + ERROR_TOO_LARGE = 5, + ERROR_UNKNOWN = 6, +}; + +struct ReadBuffer { + uint8_t *data; + size_t len; +}; +struct WriteBuffer { + const uint8_t *data; + size_t len; +}; + +class I2CBus { + public: + virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) { + ReadBuffer buf; + buf.data = buffer; + buf.len = len; + return readv(address, &buf, 1); + } + virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; + virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { + WriteBuffer buf; + buf.data = buffer; + buf.len = len; + return writev(address, &buf, 1); + } + virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; +}; + +} // namespace i2c +} // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp new file mode 100644 index 0000000000..aba412c3f7 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -0,0 +1,97 @@ +#ifdef USE_ARDUINO + +#include "i2c_bus_arduino.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace i2c { + +static const char *const TAG = "i2c.arduino"; + +void ArduinoI2CBus::setup() { +#ifdef USE_ESP32 + static uint8_t next_bus_num = 0; + if (next_bus_num == 0) + wire_ = &Wire; + else + wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + next_bus_num++; +#else + wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) +#endif + + wire_->begin(sda_pin_, scl_pin_); + wire_->setClock(frequency_); + initialized_ = true; +} +void ArduinoI2CBus::dump_config() { + ESP_LOGCONFIG(TAG, "I2C Bus:"); + ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); + ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); + ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + if (this->scan_) { + ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); + uint8_t found = 0; + for (uint8_t address = 1; address < 120; address++) { + auto err = readv(address, nullptr, 0); + + if (err == ERROR_OK) { + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); + found++; + } else if (err == ERROR_UNKNOWN) { + ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); + } + } + if (found == 0) { + ESP_LOGI(TAG, "Found no i2c devices!"); + } + } +} +ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + size_t to_request = 0; + for (size_t i = 0; i < cnt; i++) + to_request += buffers[i].len; + size_t ret = wire_->requestFrom((int) address, (int) to_request, 1); + if (ret != to_request) { + return ERROR_TIMEOUT; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + for (size_t j = 0; j < buf.len; j++) + buf.data[j] = wire_->read(); + } + return ERROR_OK; +} +ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + + wire_->beginTransmission(address); + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + size_t ret = wire_->write(buf.data, buf.len); + if (ret != buf.len) { + return ERROR_UNKNOWN; + } + } + uint8_t status = wire_->endTransmission(true); + if (status == 0) { + return ERROR_OK; + } else if (status == 1) { + // transmit buffer not large enough + return ERROR_UNKNOWN; + } else if (status == 2 || status == 3) { + return ERROR_NOT_ACKNOWLEDGED; + } + return ERROR_UNKNOWN; +} + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h new file mode 100644 index 0000000000..220027b3d4 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -0,0 +1,37 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "i2c_bus.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace i2c { + +class ArduinoI2CBus : public I2CBus, public Component { + public: + void setup() override; + void dump_config() override; + ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_scan(bool scan) { scan_ = scan; } + void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } + void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } + void set_frequency(uint32_t frequency) { frequency_ = frequency; } + + protected: + TwoWire *wire_; + bool scan_; + uint8_t sda_pin_; + uint8_t scl_pin_; + uint32_t frequency_; + bool initialized_ = false; +}; + +} // namespace i2c +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp new file mode 100644 index 0000000000..4b93b41877 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -0,0 +1,147 @@ +#ifdef USE_ESP_IDF + +#include "i2c_bus_esp_idf.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace i2c { + +static const char *const TAG = "i2c.idf"; + +void IDFI2CBus::setup() { + static i2c_port_t next_port = 0; + port_ = next_port++; + + i2c_config_t conf{}; + memset(&conf, 0, sizeof(conf)); + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = sda_pin_; + conf.sda_pullup_en = sda_pullup_enabled_; + conf.scl_io_num = scl_pin_; + conf.scl_pullup_en = scl_pullup_enabled_; + conf.master.clk_speed = frequency_; + esp_err_t err = i2c_param_config(port_, &conf); + if (err != ESP_OK) { + ESP_LOGW(TAG, "i2c_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + initialized_ = true; +} +void IDFI2CBus::dump_config() { + ESP_LOGCONFIG(TAG, "I2C Bus:"); + ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); + ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); + ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); + if (this->scan_) { + ESP_LOGI(TAG, "Scanning i2c bus for active devices..."); + uint8_t found = 0; + for (uint8_t address = 1; address < 120; address++) { + auto err = readv(address, nullptr, 0); + + if (err == ERROR_OK) { + ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address); + found++; + } else if (err == ERROR_UNKNOWN) { + ESP_LOGI(TAG, "Unknown error at address 0x%02X", address); + } + } + if (found == 0) { + ESP_LOGI(TAG, "Found no i2c devices!"); + } + } +} +ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t err = i2c_master_start(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + } + err = i2c_master_stop(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + if (err == ESP_FAIL) { + // transfer not acked + return ERROR_NOT_ACKNOWLEDGED; + } else if (err == ESP_ERR_TIMEOUT) { + return ERROR_TIMEOUT; + } else if (err != ESP_OK) { + return ERROR_UNKNOWN; + } + return ERROR_OK; +} +ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { + if (!initialized_) + return ERROR_NOT_INITIALIZED; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t err = i2c_master_start(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + for (size_t i = 0; i < cnt; i++) { + const auto &buf = buffers[i]; + if (buf.len == 0) + continue; + err = i2c_master_write(cmd, buf.data, buf.len, true); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + } + err = i2c_master_stop(cmd); + if (err != ESP_OK) { + i2c_cmd_link_delete(cmd); + return ERROR_UNKNOWN; + } + err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + if (err == ESP_FAIL) { + // transfer not acked + return ERROR_NOT_ACKNOWLEDGED; + } else if (err == ESP_ERR_TIMEOUT) { + return ERROR_TIMEOUT; + } else if (err != ESP_OK) { + return ERROR_UNKNOWN; + } + return ERROR_OK; +} + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h new file mode 100644 index 0000000000..c7e67145a3 --- /dev/null +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -0,0 +1,41 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include "i2c_bus.h" +#include "esphome/core/component.h" +#include + +namespace esphome { +namespace i2c { + +class IDFI2CBus : public I2CBus, public Component { + public: + void setup() override; + void dump_config() override; + ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; + ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void set_scan(bool scan) { scan_ = scan; } + void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } + void set_sda_pullup_enabled(bool sda_pullup_enabled) { sda_pullup_enabled_ = sda_pullup_enabled; } + void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } + void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } + void set_frequency(uint32_t frequency) { frequency_ = frequency; } + + protected: + i2c_port_t port_; + bool scan_; + uint8_t sda_pin_; + bool sda_pullup_enabled_; + uint8_t scl_pin_; + bool scl_pullup_enabled_; + uint32_t frequency_; + bool initialized_ = false; +}; + +} // namespace i2c +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index c06d487e7d..b36d05c864 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace ili9341 { @@ -185,8 +186,8 @@ void ILI9341Display::end_data_() { this->disable(); } void ILI9341Display::init_lcd_(const uint8_t *init_cmd) { uint8_t cmd, x, num_args; const uint8_t *addr = init_cmd; - while ((cmd = pgm_read_byte(addr++)) > 0) { - x = pgm_read_byte(addr++); + while ((cmd = progmem_read_byte(addr++)) > 0) { + x = progmem_read_byte(addr++); num_args = x & 0x7F; send_command(cmd, addr, num_args); addr += num_args; diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp index d1fee72866..4f6ed7702d 100644 --- a/esphome/components/improv/improv.cpp +++ b/esphome/components/improv/improv.cpp @@ -65,6 +65,7 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) { std::vector out; uint32_t length = 0; @@ -85,5 +86,6 @@ std::vector build_rpc_response(Command command, const std::vector #include #include @@ -50,6 +53,8 @@ ImprovCommand parse_improv_data(const std::vector &data); ImprovCommand parse_improv_data(const uint8_t *data, size_t length); std::vector build_rpc_response(Command command, const std::vector &datum); +#ifdef USE_ARDUINO std::vector build_rpc_response(Command command, const std::vector &datum); +#endif // USE_ARDUINO } // namespace improv diff --git a/esphome/components/ina219/ina219.cpp b/esphome/components/ina219/ina219.cpp index 506b7e06ed..609f3d0f08 100644 --- a/esphome/components/ina219/ina219.cpp +++ b/esphome/components/ina219/ina219.cpp @@ -1,5 +1,6 @@ #include "ina219.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina219 { @@ -149,7 +150,7 @@ float INA219Component::get_setup_priority() const { return setup_priority::DATA; void INA219Component::update() { if (this->bus_voltage_sensor_ != nullptr) { uint16_t raw_bus_voltage; - if (!this->read_byte_16(INA219_REGISTER_BUS_VOLTAGE, &raw_bus_voltage, 1)) { + if (!this->read_byte_16(INA219_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) { this->status_set_warning(); return; } @@ -160,8 +161,9 @@ void INA219Component::update() { if (this->shunt_voltage_sensor_ != nullptr) { uint16_t raw_shunt_voltage; - if (!this->read_byte_16(INA219_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage, 1)) { + if (!this->read_byte_16(INA219_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage)) { this->status_set_warning(); + return; } float shunt_voltage_mv = int16_t(raw_shunt_voltage) * 0.01f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_mv / 1000.0f); @@ -169,7 +171,7 @@ void INA219Component::update() { if (this->current_sensor_ != nullptr) { uint16_t raw_current; - if (!this->read_byte_16(INA219_REGISTER_CURRENT, &raw_current, 1)) { + if (!this->read_byte_16(INA219_REGISTER_CURRENT, &raw_current)) { this->status_set_warning(); return; } @@ -179,7 +181,7 @@ void INA219Component::update() { if (this->power_sensor_ != nullptr) { uint16_t raw_power; - if (!this->read_byte_16(INA219_REGISTER_POWER, &raw_power, 1)) { + if (!this->read_byte_16(INA219_REGISTER_POWER, &raw_power)) { this->status_set_warning(); return; } diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp index 701a833041..2e30a5ac01 100644 --- a/esphome/components/ina226/ina226.cpp +++ b/esphome/components/ina226/ina226.cpp @@ -1,5 +1,6 @@ #include "ina226.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina226 { @@ -96,7 +97,7 @@ float INA226Component::get_setup_priority() const { return setup_priority::DATA; void INA226Component::update() { if (this->bus_voltage_sensor_ != nullptr) { uint16_t raw_bus_voltage; - if (!this->read_byte_16(INA226_REGISTER_BUS_VOLTAGE, &raw_bus_voltage, 1)) { + if (!this->read_byte_16(INA226_REGISTER_BUS_VOLTAGE, &raw_bus_voltage)) { this->status_set_warning(); return; } @@ -106,8 +107,9 @@ void INA226Component::update() { if (this->shunt_voltage_sensor_ != nullptr) { uint16_t raw_shunt_voltage; - if (!this->read_byte_16(INA226_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage, 1)) { + if (!this->read_byte_16(INA226_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage)) { this->status_set_warning(); + return; } float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); @@ -115,7 +117,7 @@ void INA226Component::update() { if (this->current_sensor_ != nullptr) { uint16_t raw_current; - if (!this->read_byte_16(INA226_REGISTER_CURRENT, &raw_current, 1)) { + if (!this->read_byte_16(INA226_REGISTER_CURRENT, &raw_current)) { this->status_set_warning(); return; } @@ -125,7 +127,7 @@ void INA226Component::update() { if (this->power_sensor_ != nullptr) { uint16_t raw_power; - if (!this->read_byte_16(INA226_REGISTER_POWER, &raw_power, 1)) { + if (!this->read_byte_16(INA226_REGISTER_POWER, &raw_power)) { this->status_set_warning(); return; } diff --git a/esphome/components/ina3221/ina3221.cpp b/esphome/components/ina3221/ina3221.cpp index f2fcdb21eb..3f8e2d06df 100644 --- a/esphome/components/ina3221/ina3221.cpp +++ b/esphome/components/ina3221/ina3221.cpp @@ -1,5 +1,6 @@ #include "ina3221.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ina3221 { @@ -87,7 +88,7 @@ void INA3221Component::update() { float bus_voltage_v = NAN, current_a = NAN; uint16_t raw; if (channel.should_measure_bus_voltage()) { - if (!this->read_byte_16(ina3221_bus_voltage_register(i), &raw, 1)) { + if (!this->read_byte_16(ina3221_bus_voltage_register(i), &raw)) { this->status_set_warning(); return; } @@ -96,7 +97,7 @@ void INA3221Component::update() { channel.bus_voltage_sensor_->publish_state(bus_voltage_v); } if (channel.should_measure_shunt_voltage()) { - if (!this->read_byte_16(ina3221_shunt_voltage_register(i), &raw, 1)) { + if (!this->read_byte_16(ina3221_shunt_voltage_register(i), &raw)) { this->status_set_warning(); return; } diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index 81693c6d96..177bc76072 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -1,7 +1,7 @@ #include "inkbird_ibsth1_mini.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace inkbird_ibsth1_mini { @@ -88,10 +88,10 @@ bool InkbirdIBSTH1_MINI::parse_device(const esp32_ble_tracker::ESPBTDevice &devi auto humidity = ((mnf_data.data[1] << 8) + mnf_data.data[0]) / 100.0f; // Send temperature only if the value is set - if (!isnan(temperature) && this->temperature_ != nullptr) { + if (!std::isnan(temperature) && this->temperature_ != nullptr) { this->temperature_->publish_state(temperature); } - if (!isnan(external_temperature) && this->external_temperature_ != nullptr) { + if (!std::isnan(external_temperature) && this->external_temperature_ != nullptr) { this->external_temperature_->publish_state(external_temperature); } if (this->humidity_ != nullptr) { diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h index c3a9f7062d..1b6be7afe0 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace inkbird_ibsth1_mini { diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index 8e00a69751..e4c71ea717 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -8,11 +8,9 @@ from esphome.const import ( CONF_LAMBDA, CONF_PAGES, CONF_WAKEUP_PIN, - ESP_PLATFORM_ESP32, ) -DEPENDENCIES = ["i2c"] -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["i2c", "esp32"] CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin" CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin" @@ -91,6 +89,7 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(i2c.i2c_device_schema(0x48)), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), + cv.only_with_arduino, ) diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index b5cec6cf9e..8a05836db9 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -3,7 +3,9 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include namespace esphome { namespace inkplate6 { @@ -185,7 +187,7 @@ void Inkplate6::eink_on_() { delay(2); - this->read_byte(0x00, &temperature_, 0); + this->read_register(0x00, nullptr, 0); this->le_pin_->digital_write(false); this->oe_pin_->digital_write(false); @@ -208,7 +210,7 @@ void Inkplate6::fill(Color color) { memset(this->partial_buffer_, fill, this->get_buffer_length_()); } - ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); } void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); @@ -218,12 +220,12 @@ void Inkplate6::display() { this->display3b_(); } else { if (this->partial_updating_ && this->partial_update_()) { - ESP_LOGV(TAG, "Display finished (partial) (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display finished (partial) (%ums)", millis() - start_time); return; } this->display1b_(); } - ESP_LOGV(TAG, "Display finished (full) (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time); } void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); @@ -246,32 +248,32 @@ void Inkplate6::display1b_() { clean_fast_(0, 11); uint32_t clock = (1 << this->cl_pin_->get_pin()); - ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time); for (int k = 0; k < 3; k++) { buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); data = LUTB[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; data = LUTB[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -281,31 +283,31 @@ void Inkplate6::display1b_() { } delayMicroseconds(230); } - ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time); + ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time); buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); data = LUT2[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; data = LUT2[buffer_value & 0x0F]; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -314,13 +316,13 @@ void Inkplate6::display1b_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b second loop (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time); vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { data = 0b00000000; - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); send |= clock; GPIO.out_w1ts = send; @@ -336,13 +338,13 @@ void Inkplate6::display1b_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b third loop (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); vscan_start_(); eink_off_(); this->block_partial_ = false; this->partial_updates_ = 0; - ESP_LOGV(TAG, "Display1b finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time); } void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); @@ -380,11 +382,11 @@ void Inkplate6::display3b_() { pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | - (((pixel & B11100000) >> 5) << 25); + send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | + (((pixel & 0b11100000) >> 5) << 25); hscan_start_(send); - send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25) | clock; + send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | + (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; @@ -398,13 +400,13 @@ void Inkplate6::display3b_() { pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | - (((pixel & B11100000) >> 5) << 25) | clock; + send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | + (((pixel & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; - send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | - (((pixel2 & B11100000) >> 5) << 25) | clock; + send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | + (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -418,7 +420,7 @@ void Inkplate6::display3b_() { clean_fast_(3, 1); vscan_start_(); eink_off_(); - ESP_LOGV(TAG, "Display3b finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time); } bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); @@ -445,7 +447,7 @@ bool Inkplate6::partial_update_() { this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; } } - ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Partial update buffer built after (%ums)", millis() - start_time); eink_on_(); uint32_t clock = (1 << this->cl_pin_->get_pin()); @@ -454,13 +456,13 @@ bool Inkplate6::partial_update_() { const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1]; for (int i = 0; i < this->get_height_internal(); i++) { data = *(data_ptr--); - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); hscan_start_(send); for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) { data = *(data_ptr--); - send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25) | clock; + send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25) | clock; GPIO.out_w1ts = send; GPIO.out_w1tc = send; } @@ -469,7 +471,7 @@ bool Inkplate6::partial_update_() { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Partial update loop k=%d (%lums)", k, millis() - start_time); + ESP_LOGV(TAG, "Partial update loop k=%d (%ums)", k, millis() - start_time); } clean_fast_(2, 2); clean_fast_(3, 1); @@ -477,7 +479,7 @@ bool Inkplate6::partial_update_() { eink_off_(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); - ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time); return true; } void Inkplate6::vscan_start_() { @@ -538,7 +540,7 @@ void Inkplate6::clean() { clean_fast_(0, 8); // Black to Black clean_fast_(2, 1); // Black to White clean_fast_(1, 10); // White to White - ESP_LOGV(TAG, "Clean finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time); } void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); @@ -547,16 +549,16 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { eink_on_(); uint8_t data = 0; if (c == 0) // White - data = B10101010; + data = 0b10101010; else if (c == 1) // Black - data = B01010101; + data = 0b01010101; else if (c == 2) // Discharge - data = B00000000; + data = 0b00000000; else if (c == 3) // Skip - data = B11111111; + data = 0b11111111; - uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | - (((data & B11100000) >> 5) << 25); + uint32_t send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | + (((data & 0b11100000) >> 5) << 25); uint32_t clock = (1 << this->cl_pin_->get_pin()); for (int k = 0; k < rep; k++) { @@ -576,46 +578,46 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { vscan_end_(); } delayMicroseconds(230); - ESP_LOGV(TAG, "Clean fast rep loop %d finished (%lums)", k, millis() - start_time); + ESP_LOGV(TAG, "Clean fast rep loop %d finished (%ums)", k, millis() - start_time); } - ESP_LOGV(TAG, "Clean fast finished (%lums)", millis() - start_time); + ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time); } void Inkplate6::pins_z_state_() { - this->ckv_pin_->pin_mode(INPUT); - this->sph_pin_->pin_mode(INPUT); + this->ckv_pin_->pin_mode(gpio::FLAG_INPUT); + this->sph_pin_->pin_mode(gpio::FLAG_INPUT); - this->oe_pin_->pin_mode(INPUT); - this->gmod_pin_->pin_mode(INPUT); - this->spv_pin_->pin_mode(INPUT); + this->oe_pin_->pin_mode(gpio::FLAG_INPUT); + this->gmod_pin_->pin_mode(gpio::FLAG_INPUT); + this->spv_pin_->pin_mode(gpio::FLAG_INPUT); - this->display_data_0_pin_->pin_mode(INPUT); - this->display_data_1_pin_->pin_mode(INPUT); - this->display_data_2_pin_->pin_mode(INPUT); - this->display_data_3_pin_->pin_mode(INPUT); - this->display_data_4_pin_->pin_mode(INPUT); - this->display_data_5_pin_->pin_mode(INPUT); - this->display_data_6_pin_->pin_mode(INPUT); - this->display_data_7_pin_->pin_mode(INPUT); + this->display_data_0_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_1_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_2_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_3_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_4_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_5_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT); + this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT); } void Inkplate6::pins_as_outputs_() { - this->ckv_pin_->pin_mode(OUTPUT); - this->sph_pin_->pin_mode(OUTPUT); + this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->oe_pin_->pin_mode(OUTPUT); - this->gmod_pin_->pin_mode(OUTPUT); - this->spv_pin_->pin_mode(OUTPUT); + this->oe_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->gmod_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->spv_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->display_data_0_pin_->pin_mode(OUTPUT); - this->display_data_1_pin_->pin_mode(OUTPUT); - this->display_data_2_pin_->pin_mode(OUTPUT); - this->display_data_3_pin_->pin_mode(OUTPUT); - this->display_data_4_pin_->pin_mode(OUTPUT); - this->display_data_5_pin_->pin_mode(OUTPUT); - this->display_data_6_pin_->pin_mode(OUTPUT); - this->display_data_7_pin_->pin_mode(OUTPUT); + this->display_data_0_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_1_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_2_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_3_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_4_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_5_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_6_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->display_data_7_pin_->pin_mode(gpio::FLAG_OUTPUT); } } // namespace inkplate6 } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index f2821b22a9..56e95e95bb 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -1,26 +1,29 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/display/display_buffer.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO namespace esphome { namespace inkplate6 { class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { public: - const uint8_t LUT2[16] = {B10101010, B10101001, B10100110, B10100101, B10011010, B10011001, B10010110, B10010101, - B01101010, B01101001, B01100110, B01100101, B01011010, B01011001, B01010110, B01010101}; - const uint8_t LUTW[16] = {B11111111, B11111110, B11111011, B11111010, B11101111, B11101110, B11101011, B11101010, - B10111111, B10111110, B10111011, B10111010, B10101111, B10101110, B10101011, B10101010}; - const uint8_t LUTB[16] = {B11111111, B11111101, B11110111, B11110101, B11011111, B11011101, B11010111, B11010101, - B01111111, B01111101, B01110111, B01110101, B01011111, B01011101, B01010111, B01010101}; - const uint8_t pixelMaskLUT[8] = {B00000001, B00000010, B00000100, B00001000, - B00010000, B00100000, B01000000, B10000000}; - const uint8_t pixelMaskGLUT[2] = {B00001111, B11110000}; + const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001, + 0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101, + 0b01011010, 0b01011001, 0b01010110, 0b01010101}; + const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110, + 0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010, + 0b10101111, 0b10101110, 0b10101011, 0b10101010}; + const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101, + 0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101, + 0b01011111, 0b01011101, 0b01010111, 0b01010101}; + const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, + 0b00010000, 0b00100000, 0b01000000, 0b10000000}; + const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000}; const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; @@ -40,20 +43,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } - void set_display_data_0_pin(GPIOPin *data) { this->display_data_0_pin_ = data; } - void set_display_data_1_pin(GPIOPin *data) { this->display_data_1_pin_ = data; } - void set_display_data_2_pin(GPIOPin *data) { this->display_data_2_pin_ = data; } - void set_display_data_3_pin(GPIOPin *data) { this->display_data_3_pin_ = data; } - void set_display_data_4_pin(GPIOPin *data) { this->display_data_4_pin_ = data; } - void set_display_data_5_pin(GPIOPin *data) { this->display_data_5_pin_ = data; } - void set_display_data_6_pin(GPIOPin *data) { this->display_data_6_pin_ = data; } - void set_display_data_7_pin(GPIOPin *data) { this->display_data_7_pin_ = data; } + void set_display_data_0_pin(InternalGPIOPin *data) { this->display_data_0_pin_ = data; } + void set_display_data_1_pin(InternalGPIOPin *data) { this->display_data_1_pin_ = data; } + void set_display_data_2_pin(InternalGPIOPin *data) { this->display_data_2_pin_ = data; } + void set_display_data_3_pin(InternalGPIOPin *data) { this->display_data_3_pin_ = data; } + void set_display_data_4_pin(InternalGPIOPin *data) { this->display_data_4_pin_ = data; } + void set_display_data_5_pin(InternalGPIOPin *data) { this->display_data_5_pin_ = data; } + void set_display_data_6_pin(InternalGPIOPin *data) { this->display_data_6_pin_ = data; } + void set_display_data_7_pin(InternalGPIOPin *data) { this->display_data_7_pin_ = data; } void set_ckv_pin(GPIOPin *ckv) { this->ckv_pin_ = ckv; } - void set_cl_pin(GPIOPin *cl) { this->cl_pin_ = cl; } + void set_cl_pin(InternalGPIOPin *cl) { this->cl_pin_ = cl; } void set_gpio0_enable_pin(GPIOPin *gpio0_enable) { this->gpio0_enable_pin_ = gpio0_enable; } void set_gmod_pin(GPIOPin *gmod) { this->gmod_pin_ = gmod; } - void set_le_pin(GPIOPin *le) { this->le_pin_ = le; } + void set_le_pin(InternalGPIOPin *le) { this->le_pin_ = le; } void set_oe_pin(GPIOPin *oe) { this->oe_pin_ = oe; } void set_powerup_pin(GPIOPin *powerup) { this->powerup_pin_ = powerup; } void set_sph_pin(GPIOPin *sph) { this->sph_pin_ = sph; } @@ -130,20 +133,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public bool greyscale_; bool partial_updating_; - GPIOPin *display_data_0_pin_; - GPIOPin *display_data_1_pin_; - GPIOPin *display_data_2_pin_; - GPIOPin *display_data_3_pin_; - GPIOPin *display_data_4_pin_; - GPIOPin *display_data_5_pin_; - GPIOPin *display_data_6_pin_; - GPIOPin *display_data_7_pin_; + InternalGPIOPin *display_data_0_pin_; + InternalGPIOPin *display_data_1_pin_; + InternalGPIOPin *display_data_2_pin_; + InternalGPIOPin *display_data_3_pin_; + InternalGPIOPin *display_data_4_pin_; + InternalGPIOPin *display_data_5_pin_; + InternalGPIOPin *display_data_6_pin_; + InternalGPIOPin *display_data_7_pin_; GPIOPin *ckv_pin_; - GPIOPin *cl_pin_; + InternalGPIOPin *cl_pin_; GPIOPin *gpio0_enable_pin_; GPIOPin *gmod_pin_; - GPIOPin *le_pin_; + InternalGPIOPin *le_pin_; GPIOPin *oe_pin_; GPIOPin *powerup_pin_; GPIOPin *sph_pin_; @@ -155,4 +158,4 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public } // namespace inkplate6 } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index 9a0f9fc58b..2a398e5240 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -1,6 +1,7 @@ #include "integration_sensor.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace integration { @@ -9,7 +10,7 @@ static const char *const TAG = "integration"; void IntegrationSensor::setup() { if (this->restore_) { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); float preference_value = 0; this->rtc_.load(&preference_value); this->result_ = preference_value; diff --git a/esphome/components/integration/integration_sensor.h b/esphome/components/integration/integration_sensor.h index c575de094c..a3bdfbbb2b 100644 --- a/esphome/components/integration/integration_sensor.h +++ b/esphome/components/integration/integration_sensor.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/automation.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 9879754d9e..fda0a552f1 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -1,9 +1,15 @@ import esphome.codegen as cg +import esphome.config_validation as cv from esphome.core import coroutine_with_priority CODEOWNERS = ["@OttoWinter"] json_ns = cg.esphome_ns.namespace("json") +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_arduino, +) + @coroutine_with_priority(1.0) async def to_code(config): diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index aab5b1ce9b..12c5beb73f 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "json_util.h" #include "esphome/core/log.h" @@ -113,3 +115,5 @@ VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-non-cons } // namespace json } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 65b2496b85..577510e63a 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/helpers.h" @@ -62,3 +64,5 @@ extern VectorJsonBuffer global_json_buffer; // NOLINT(cppcoreguidelines-avoid-n } // namespace json } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 4c0d0ab3ea..ddd7d6a6b3 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -1,6 +1,7 @@ #include "lcd_display.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace lcd_base { diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index 5c1656ec3e..b0344d313c 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -30,8 +30,15 @@ void GPIOLCDDisplay::dump_config() { LOG_PIN(" RW Pin: ", this->rw_pin_); LOG_PIN(" Enable Pin: ", this->enable_pin_); - for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { - ESP_LOGCONFIG(TAG, " Data Pin %u" LOG_PIN_PATTERN, i, LOG_PIN_ARGS(this->data_pins_[i])); + LOG_PIN(" Data Pin 0: ", data_pins_[0]); + LOG_PIN(" Data Pin 1: ", data_pins_[1]); + LOG_PIN(" Data Pin 2: ", data_pins_[2]); + LOG_PIN(" Data Pin 3: ", data_pins_[3]); + if (!is_four_bit_mode()) { + LOG_PIN(" Data Pin 4: ", data_pins_[4]); + LOG_PIN(" Data Pin 5: ", data_pins_[5]); + LOG_PIN(" Data Pin 6: ", data_pins_[6]); + LOG_PIN(" Data Pin 7: ", data_pins_[7]); } LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.h b/esphome/components/lcd_gpio/gpio_lcd_display.h index 01f6f95d9a..aba254a90a 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.h +++ b/esphome/components/lcd_gpio/gpio_lcd_display.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/lcd_base/lcd_display.h" namespace esphome { diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.cpp b/esphome/components/lcd_pcf8574/pcf8574_display.cpp index 4830b6f223..5b00b08aff 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.cpp +++ b/esphome/components/lcd_pcf8574/pcf8574_display.cpp @@ -1,5 +1,6 @@ #include "pcf8574_display.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace lcd_pcf8574 { diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 45bee5b871..77610a476f 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -1,40 +1,20 @@ #include "ledc_output.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 +#ifdef USE_ARDUINO #include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { namespace ledc { static const char *const TAG = "ledc.output"; -void LEDCOutput::write_state(float state) { - if (this->pin_->is_inverted()) - state = 1.0f - state; - - this->duty_ = state; - const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; - const float duty_rounded = roundf(state * max_duty); - auto duty = static_cast(duty_rounded); - ledcWrite(this->channel_, duty); -} - -void LEDCOutput::setup() { - this->update_frequency(this->frequency_); - this->turn_off(); - // Attach pin after setting default value - ledcAttachPin(this->pin_->get_pin(), this->channel_); -} - -void LEDCOutput::dump_config() { - ESP_LOGCONFIG(TAG, "LEDC Output:"); - LOG_PIN(" Pin ", this->pin_); - ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); -} - float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { const float max_div_num = ((1 << 20) - 1) / 256.0f; @@ -50,6 +30,67 @@ optional ledc_bit_depth_for_frequency(float frequency) { return {}; } +void LEDCOutput::write_state(float state) { + if (this->pin_->is_inverted()) + state = 1.0f - state; + + this->duty_ = state; + const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + +#ifdef USE_ARDUINO + ledcWrite(this->channel_, duty); +#endif +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto chan_num = static_cast(channel_ % 8); + ledc_set_duty(speed_mode, chan_num, duty); + ledc_update_duty(speed_mode, chan_num); +#endif +} + +void LEDCOutput::setup() { +#ifdef USE_ARDUINO + this->update_frequency(this->frequency_); + this->turn_off(); + // Attach pin after setting default value + ledcAttachPin(this->pin_->get_pin(), this->channel_); +#endif +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto timer_num = static_cast((channel_ % 8) / 2); + auto chan_num = static_cast(channel_ % 8); + + bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); + + ledc_timer_config_t timer_conf{}; + timer_conf.speed_mode = speed_mode; + timer_conf.duty_resolution = static_cast(bit_depth_); + timer_conf.timer_num = timer_num; + timer_conf.freq_hz = (uint32_t) frequency_; + timer_conf.clk_cfg = LEDC_AUTO_CLK; + ledc_timer_config(&timer_conf); + + ledc_channel_config_t chan_conf{}; + chan_conf.gpio_num = pin_->get_pin(); + chan_conf.speed_mode = speed_mode; + chan_conf.channel = chan_num; + chan_conf.intr_type = LEDC_INTR_DISABLE; + chan_conf.timer_sel = timer_num; + chan_conf.duty = inverted_ == pin_->is_inverted() ? 0 : (1U << bit_depth_); + chan_conf.hpoint = 0; + ledc_channel_config(&chan_conf); +#endif +} + +void LEDCOutput::dump_config() { + ESP_LOGCONFIG(TAG, "LEDC Output:"); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); +} + void LEDCOutput::update_frequency(float frequency) { auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency); if (!bit_depth_opt.has_value()) { @@ -58,7 +99,21 @@ void LEDCOutput::update_frequency(float frequency) { } this->bit_depth_ = bit_depth_opt.value_or(8); this->frequency_ = frequency; +#ifdef USE_ARDUINO ledcSetup(this->channel_, frequency, this->bit_depth_); +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; + auto timer_num = static_cast((channel_ % 8) / 2); + + ledc_timer_config_t timer_conf{}; + timer_conf.speed_mode = speed_mode; + timer_conf.duty_resolution = static_cast(bit_depth_); + timer_conf.timer_num = timer_num; + timer_conf.freq_hz = (uint32_t) frequency_; + timer_conf.clk_cfg = LEDC_AUTO_CLK; + ledc_timer_config(&timer_conf); +#endif // re-apply duty this->write_state(this->duty_); } diff --git a/esphome/components/ledc/ledc_output.h b/esphome/components/ledc/ledc_output.h index b3b14fe855..f810ce1e35 100644 --- a/esphome/components/ledc/ledc_output.h +++ b/esphome/components/ledc/ledc_output.h @@ -1,11 +1,11 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/output/float_output.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ledc { @@ -14,7 +14,7 @@ extern uint8_t next_ledc_channel; class LEDCOutput : public output::FloatOutput, public Component { public: - explicit LEDCOutput(GPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } + explicit LEDCOutput(InternalGPIOPin *pin) : pin_(pin) { this->channel_ = next_ledc_channel++; } void set_channel(uint8_t channel) { this->channel_ = channel; } void set_frequency(float frequency) { this->frequency_ = frequency; } @@ -31,7 +31,7 @@ class LEDCOutput : public output::FloatOutput, public Component { void write_state(float state) override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; uint8_t channel_{}; uint8_t bit_depth_{}; float frequency_{}; diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 0f5f186ff5..895dcc998b 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -7,10 +7,9 @@ from esphome.const import ( CONF_FREQUENCY, CONF_ID, CONF_PIN, - ESP_PLATFORM_ESP32, ) -ESP_PLATFORMS = [ESP_PLATFORM_ESP32] +DEPENDENCIES = ["esp32"] def calc_max_frequency(bit_depth): diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 398546a09f..f888006b17 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -53,7 +53,7 @@ void LightState::setup() { case LIGHT_RESTORE_DEFAULT_ON: case LIGHT_RESTORE_INVERTED_DEFAULT_OFF: case LIGHT_RESTORE_INVERTED_DEFAULT_ON: - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); // Attempt to load from preferences, else fall back to default values if (!this->rtc_.load(&recovered)) { recovered.state = false; diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index c5181abd4f..dd904d0eed 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include "light_color_values.h" namespace esphome { @@ -9,6 +10,8 @@ namespace light { /// Base class for all light color transformers, such as transitions or flashes. class LightTransformer { public: + virtual ~LightTransformer() = default; + void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) { this->start_time_ = millis(); this->length_ = length; diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index b821e8c5d8..bc1bc6bb41 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -214,7 +214,7 @@ def validate_printf(value): (?:\.(?:\d+|\*))? # precision (?:h|l|ll|w|I|I32|I64)? # size [cCdiouxXeEfgGaAnpsSZ] # type - ) + ) """ # noqa matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X) if len(matches) != len(value[CONF_ARGS]): diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 59dfa93429..045f7059a9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -1,9 +1,15 @@ #include "logger.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP_IDF +#include "freertos/FreeRTOS.h" +#include +#endif + +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include #endif -#include +#include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace logger { @@ -63,11 +69,11 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr recursion_guard_ = true; this->reset_buffer_(); // copy format string - const char *format_pgm_p = (PGM_P) format; + auto *format_pgm_p = reinterpret_cast(format); size_t len = 0; char ch = '.'; while (!this->is_buffer_full_() && ch != '\0') { - this->tx_buffer_[this->tx_buffer_at_++] = ch = pgm_read_byte(format_pgm_p++); + this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); } // Buffer full form copying format if (this->is_buffer_full_()) @@ -105,19 +111,26 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->set_null_terminator_(); const char *msg = this->tx_buffer_ + offset; +#ifdef USE_ARDUINO if (this->baud_rate_ > 0) this->hw_serial_->println(msg); -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + uart_write_bytes(uart_num_, msg, strlen(msg)); + uart_write_bytes(uart_num_, "\n", 1); +#endif + +#ifdef USE_ESP32 // Suppress network-logging if memory constrained, but still log to serial // ports. In some configurations (eg BLE enabled) there may be some transient // memory exhaustion, and trying to log when OOM can lead to a crash. Skipping // here usually allows the stack to recover instead. // See issue #1234 for analysis. - if (xPortGetFreeHeapSize() > 2048) - this->log_callback_.call(level, tag, msg); -#else - this->log_callback_.call(level, tag, msg); + if (xPortGetFreeHeapSize() < 2048) + return; #endif + + this->log_callback_.call(level, tag, msg); } Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) @@ -128,9 +141,10 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) void Logger::pre_setup() { if (this->baud_rate_ > 0) { +#ifdef USE_ARDUINO switch (this->uart_) { case UART_SELECTION_UART0: -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 case UART_SELECTION_UART0_SWAP: #endif this->hw_serial_ = &Serial; @@ -138,7 +152,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 case UART_SELECTION_UART2: #if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3 // FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has @@ -148,23 +162,51 @@ void Logger::pre_setup() { break; #endif } +#endif // USE_ARDUINO +#ifdef USE_ESP_IDF + uart_num_ = UART_NUM_0; + switch (uart_) { + case UART_SELECTION_UART0: + uart_num_ = UART_NUM_0; + break; + case UART_SELECTION_UART1: + uart_num_ = UART_NUM_1; + break; + case UART_SELECTION_UART2: + uart_num_ = UART_NUM_2; + break; + } + uart_config_t uart_config = { + .baud_rate = (int) baud_rate_, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + }; + uart_param_config(uart_num_, &uart_config); + const int uart_buffer_size = tx_buffer_size_; + // Install UART driver using an event queue here + uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); +#endif +#ifdef USE_ARDUINO this->hw_serial_->begin(this->baud_rate_); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 if (this->uart_ == UART_SELECTION_UART0_SWAP) { this->hw_serial_->swap(); } this->hw_serial_->setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); #endif +#endif // USE_ARDUINO } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 else { uart_set_debug(UART_NO); } #endif global_logger = this; -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) esp_log_set_vprintf(esp_idf_log_vprintf_); if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { esp_log_level_set("*", ESP_LOG_VERBOSE); @@ -183,10 +225,10 @@ void Logger::add_on_log_callback(std::function + +#ifdef USE_ARDUINO +#include +#endif +#ifdef USE_ESP_IDF +#include +#endif namespace esphome { @@ -17,11 +24,11 @@ namespace logger { enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, -#ifdef ARDUINO_ARCH_ESP32 - UART_SELECTION_UART2 +#ifdef USE_ESP32 + UART_SELECTION_UART2, #endif -#ifdef ARDUINO_ARCH_ESP8266 - UART_SELECTION_UART0_SWAP +#ifdef USE_ESP8266 + UART_SELECTION_UART0_SWAP, #endif }; @@ -32,7 +39,12 @@ class Logger : public Component { /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } +#ifdef USE_ARDUINO HardwareSerial *get_hw_serial() const { return hw_serial_; } +#endif +#ifdef USE_ESP_IDF + uart_port_t get_uart_num() const { return uart_num_; } +#endif /// Get the UART used by the logger. UARTSelection get_uart() const; @@ -106,7 +118,12 @@ class Logger : public Component { int tx_buffer_at_{0}; int tx_buffer_size_{0}; UARTSelection uart_{UART_SELECTION_UART0}; +#ifdef USE_ARDUINO HardwareSerial *hw_serial_{nullptr}; +#endif +#ifdef USE_ESP_IDF + uart_port_t uart_num_; +#endif struct LogLevelOverride { std::string tag; int level; diff --git a/esphome/components/max31856/max31856.cpp b/esphome/components/max31856/max31856.cpp index 9b627e21a2..9300916fdc 100644 --- a/esphome/components/max31856/max31856.cpp +++ b/esphome/components/max31856/max31856.cpp @@ -163,7 +163,7 @@ void MAX31856Sensor::write_register_(uint8_t reg, uint8_t value) { ESP_LOGV(TAG, "write_register_ 0x%02X: 0x%02X", reg, value); } -const uint8_t MAX31856Sensor::read_register_(uint8_t reg) { +uint8_t MAX31856Sensor::read_register_(uint8_t reg) { ESP_LOGVV(TAG, "read_register_ 0x%02X", reg); this->enable(); ESP_LOGVV(TAG, "write_byte reg=0x%02X", reg); @@ -175,7 +175,7 @@ const uint8_t MAX31856Sensor::read_register_(uint8_t reg) { return value; } -const uint32_t MAX31856Sensor::read_register24_(uint8_t reg) { +uint32_t MAX31856Sensor::read_register24_(uint8_t reg) { ESP_LOGVV(TAG, "read_register_24_ 0x%02X", reg); this->enable(); ESP_LOGVV(TAG, "write_byte reg=0x%02X", reg); diff --git a/esphome/components/max31856/max31856.h b/esphome/components/max31856/max31856.h index 779eb52c8e..157aad433c 100644 --- a/esphome/components/max31856/max31856.h +++ b/esphome/components/max31856/max31856.h @@ -82,8 +82,8 @@ class MAX31856Sensor : public sensor::Sensor, protected: MAX31856ConfigFilter filter_; - const uint8_t read_register_(uint8_t reg); - const uint32_t read_register24_(uint8_t reg); + uint8_t read_register_(uint8_t reg); + uint32_t read_register24_(uint8_t reg); void write_register_(uint8_t reg, uint8_t value); void one_shot_temperature_(); diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index daadc26cdc..91946cde2c 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -156,7 +156,7 @@ void MAX31865Sensor::write_register_(uint8_t reg, uint8_t value) { ESP_LOGVV(TAG, "write_register_ 0x%02X: 0x%02X", reg, value); } -const uint8_t MAX31865Sensor::read_register_(uint8_t reg) { +uint8_t MAX31865Sensor::read_register_(uint8_t reg) { this->enable(); this->write_byte(reg); const uint8_t value(this->read_byte()); @@ -165,7 +165,7 @@ const uint8_t MAX31865Sensor::read_register_(uint8_t reg) { return value; } -const uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { +uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { this->enable(); this->write_byte(reg); const uint8_t msb(this->read_byte()); @@ -176,7 +176,7 @@ const uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) { return value; } -float MAX31865Sensor::calc_temperature_(const float &rtd_ratio) { +float MAX31865Sensor::calc_temperature_(float rtd_ratio) { // Based loosely on Adafruit's library: https://github.com/adafruit/Adafruit_MAX31865 // Mainly based on formulas provided by Analog: // http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf diff --git a/esphome/components/max31865/max31865.h b/esphome/components/max31865/max31865.h index 393dd4b434..b83753a678 100644 --- a/esphome/components/max31865/max31865.h +++ b/esphome/components/max31865/max31865.h @@ -49,9 +49,9 @@ class MAX31865Sensor : public sensor::Sensor, void read_data_(); void write_config_(uint8_t mask, uint8_t bits, uint8_t start_position = 0); void write_register_(uint8_t reg, uint8_t value); - const uint8_t read_register_(uint8_t reg); - const uint16_t read_register_16_(uint8_t reg); - float calc_temperature_(const float &rtd_ratio); + uint8_t read_register_(uint8_t reg); + uint16_t read_register_16_(uint8_t reg); + float calc_temperature_(float rtd_ratio); }; } // namespace max31865 diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 97886e53fa..960ac58071 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -1,6 +1,7 @@ #include "max7219.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace max7219 { @@ -172,7 +173,7 @@ uint8_t MAX7219Component::print(uint8_t start_pos, const char *str) { for (; *str != '\0'; str++) { uint8_t data = MAX7219_UNKNOWN_CHAR; if (*str >= ' ' && *str <= '~') - data = pgm_read_byte(&MAX7219_ASCII_TO_RAW[*str - ' ']); + data = progmem_read_byte(&MAX7219_ASCII_TO_RAW[*str - ' ']); if (data == MAX7219_UNKNOWN_CHAR) { ESP_LOGW(TAG, "Encountered character '%c' with no MAX7219 representation while translating string!", *str); diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 520694af9d..4fedd3d312 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -1,6 +1,7 @@ #include "max7219digit.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include "max7219font.h" namespace esphome { @@ -212,7 +213,7 @@ void MAX7219Component::scroll_left() { void MAX7219Component::send_char(uint8_t chip, uint8_t data) { // get this character from PROGMEM for (uint8_t i = 0; i < 8; i++) - this->max_displaybuffer_[chip * 8 + i] = pgm_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); + this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); } // end of send_char // send one character (data) to position (chip) diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 83f45e3a00..02fe8b6f42 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -54,8 +54,8 @@ class MAX7219Component : public PollingComponent, void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; void set_reverse(bool on_off) { this->reverse_ = on_off; }; - void send_char(byte chip, byte data); - void send64pixels(byte chip, const byte pixels[8]); + void send_char(uint8_t chip, uint8_t data); + void send64pixels(uint8_t chip, const uint8_t pixels[8]); void scroll_left(); void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell); diff --git a/esphome/components/max7219digit/max7219font.h b/esphome/components/max7219digit/max7219font.h index 3d42d1cc2d..22d64d1ecd 100644 --- a/esphome/components/max7219digit/max7219font.h +++ b/esphome/components/max7219digit/max7219font.h @@ -1,11 +1,13 @@ #pragma once +#include "esphome/core/hal.h" + namespace esphome { namespace max7219digit { // bit patterns for the CP437 font -const byte MAX7219_DOT_MATRIX_FONT[256][8] PROGMEM = { +const uint8_t MAX7219_DOT_MATRIX_FONT[256][8] PROGMEM = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x00 {0x7E, 0x81, 0x95, 0xB1, 0xB1, 0x95, 0x81, 0x7E}, // 0x01 {0x7E, 0xFF, 0xEB, 0xCF, 0xCF, 0xEB, 0xFF, 0x7E}, // 0x02 diff --git a/esphome/components/mcp23008/mcp23008.h b/esphome/components/mcp23008/mcp23008.h index 42c8e497fa..406ce0b419 100644 --- a/esphome/components/mcp23008/mcp23008.h +++ b/esphome/components/mcp23008/mcp23008.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x08_base/mcp23x08_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 4d9657e794..c1209a9627 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -2,17 +2,19 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_OUTPUT, +) DEPENDENCIES = ["i2c"] MULTI_CONF = True mcp23016_ns = cg.esphome_ns.namespace("mcp23016") -MCP23016GPIOMode = mcp23016_ns.enum("MCP23016GPIOMode") -MCP23016_GPIO_MODES = { - "INPUT": MCP23016GPIOMode.MCP23016_INPUT, - "OUTPUT": MCP23016GPIOMode.MCP23016_OUTPUT, -} MCP23016 = mcp23016_ns.class_("MCP23016", cg.Component, i2c.I2CDevice) MCP23016GPIOPin = mcp23016_ns.class_("MCP23016GPIOPin", cg.GPIOPin) @@ -34,34 +36,41 @@ async def to_code(config): await i2c.register_i2c_device(var, config) +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + return value + + CONF_MCP23016 = "mcp23016" -MCP23016_OUTPUT_PIN_SCHEMA = cv.Schema( +MCP23016_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(MCP23016GPIOPin), cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - MCP23016_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) -MCP23016_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_MCP23016): cv.use_id(MCP23016), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23016_GPIO_MODES, upper=True + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_MCP23016, (MCP23016_OUTPUT_PIN_SCHEMA, MCP23016_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016, MCP23016_PIN_SCHEMA) async def mcp23016_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_MCP23016]) - return MCP23016GPIOPin.new( - parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] - ) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index f2b55fe2e2..a8df4e1745 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -1,5 +1,6 @@ #include "mcp23016.h" #include "esphome/core/log.h" +#include namespace esphome { namespace mcp23016 { @@ -29,17 +30,12 @@ void MCP23016::digital_write(uint8_t pin, bool value) { uint8_t reg_addr = pin < 8 ? MCP23016_OLAT0 : MCP23016_OLAT1; this->update_reg_(pin, value, reg_addr); } -void MCP23016::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = pin < 8 ? MCP23016_IODIR0 : MCP23016_IODIR1; - switch (mode) { - case MCP23016_INPUT: - this->update_reg_(pin, true, iodir); - break; - case MCP23016_OUTPUT: - this->update_reg_(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg_(pin, true, iodir); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg_(pin, false, iodir); } } float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; } @@ -80,12 +76,15 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { } } -MCP23016GPIOPin::MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} -void MCP23016GPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23016GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +void MCP23016GPIOPin::setup() { pin_mode(flags_); } +void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string MCP23016GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); + return buffer; +} } // namespace mcp23016 } // namespace esphome diff --git a/esphome/components/mcp23016/mcp23016.h b/esphome/components/mcp23016/mcp23016.h index 53502f80eb..a4890b4120 100644 --- a/esphome/components/mcp23016/mcp23016.h +++ b/esphome/components/mcp23016/mcp23016.h @@ -1,18 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace mcp23016 { -/// Modes for MCP23016 pins -enum MCP23016GPIOMode : uint8_t { - MCP23016_INPUT = INPUT, // 0x00 - MCP23016_OUTPUT = OUTPUT // 0x01 -}; - enum MCP23016GPIORegisters { // 0 side MCP23016_GP0 = 0x00, @@ -38,7 +32,7 @@ class MCP23016 : public Component, public i2c::I2CDevice { bool digital_read(uint8_t pin); void digital_write(uint8_t pin, bool value); - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; @@ -56,15 +50,22 @@ class MCP23016 : public Component, public i2c::I2CDevice { class MCP23016GPIOPin : public GPIOPin { public: - MCP23016GPIOPin(MCP23016 *parent, uint8_t pin, uint8_t mode, bool inverted = false); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(MCP23016 *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: MCP23016 *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace mcp23016 diff --git a/esphome/components/mcp23017/mcp23017.h b/esphome/components/mcp23017/mcp23017.h index fd9086a492..8959e06a41 100644 --- a/esphome/components/mcp23017/mcp23017.h +++ b/esphome/components/mcp23017/mcp23017.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x17_base/mcp23x17_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { diff --git a/esphome/components/mcp23s08/mcp23s08.h b/esphome/components/mcp23s08/mcp23s08.h index 4ca02c54fc..a2a6be880a 100644 --- a/esphome/components/mcp23s08/mcp23s08.h +++ b/esphome/components/mcp23s08/mcp23s08.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x08_base/mcp23x08_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/spi/spi.h" namespace esphome { diff --git a/esphome/components/mcp23s17/mcp23s17.h b/esphome/components/mcp23s17/mcp23s17.h index 1ced144c23..cb5d6cfcd8 100644 --- a/esphome/components/mcp23s17/mcp23s17.h +++ b/esphome/components/mcp23s17/mcp23s17.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23x17_base/mcp23x17_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/spi/spi.h" namespace esphome { diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 2b047fa288..2137b36921 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -19,22 +19,16 @@ void MCP23X08Base::digital_write(uint8_t pin, bool value) { this->update_reg(pin, value, reg_addr); } -void MCP23X08Base::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23X08Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = mcp23x08_base::MCP23X08_IODIR; uint8_t gppu = mcp23x08_base::MCP23X08_GPPU; - switch (mode) { - case mcp23xxx_base::MCP23XXX_INPUT: - this->update_reg(pin, true, iodir); - break; - case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case mcp23xxx_base::MCP23XXX_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg(pin, true, iodir); + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg(pin, false, iodir); } } diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.h b/esphome/components/mcp23x08_base/mcp23x08_base.h index 5e2c1a047f..910519119b 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.h +++ b/esphome/components/mcp23x08_base/mcp23x08_base.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23x08_base { @@ -26,7 +26,7 @@ class MCP23X08Base : public mcp23xxx_base::MCP23XXXBase { public: bool digital_read(uint8_t pin) override; void digital_write(uint8_t pin, bool value) override; - void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; protected: diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 72dec2d457..e975670faa 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -19,22 +19,16 @@ void MCP23X17Base::digital_write(uint8_t pin, bool value) { this->update_reg(pin, value, reg_addr); } -void MCP23X17Base::pin_mode(uint8_t pin, uint8_t mode) { +void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t iodir = pin < 8 ? mcp23x17_base::MCP23X17_IODIRA : mcp23x17_base::MCP23X17_IODIRB; uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; - switch (mode) { - case mcp23xxx_base::MCP23XXX_INPUT: - this->update_reg(pin, true, iodir); - break; - case mcp23xxx_base::MCP23XXX_INPUT_PULLUP: - this->update_reg(pin, true, iodir); - this->update_reg(pin, true, gppu); - break; - case mcp23xxx_base::MCP23XXX_OUTPUT: - this->update_reg(pin, false, iodir); - break; - default: - break; + if (flags == gpio::FLAG_INPUT) { + this->update_reg(pin, true, iodir); + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_reg(pin, false, iodir); } } diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.h b/esphome/components/mcp23x17_base/mcp23x17_base.h index 1bbcb97041..3d50ee8c03 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.h +++ b/esphome/components/mcp23x17_base/mcp23x17_base.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/mcp23xxx_base/mcp23xxx_base.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23x17_base { @@ -38,7 +38,7 @@ class MCP23X17Base : public mcp23xxx_base::MCP23XXXBase { public: bool digital_read(uint8_t pin) override; void digital_write(uint8_t pin, bool value) override; - void pin_mode(uint8_t pin, uint8_t mode) override; + void pin_mode(uint8_t pin, gpio::Flags flags) override; void pin_interrupt_mode(uint8_t pin, mcp23xxx_base::MCP23XXXInterruptMode interrupt_mode) override; protected: diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index c22d377b3c..f2c2706416 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -3,11 +3,14 @@ import esphome.config_validation as cv from esphome import pins from esphome.const import ( CONF_ID, + CONF_INPUT, CONF_NUMBER, CONF_MODE, CONF_INVERTED, CONF_INTERRUPT, CONF_OPEN_DRAIN_INTERRUPT, + CONF_OUTPUT, + CONF_PULLUP, ) from esphome.core import coroutine @@ -47,26 +50,29 @@ async def register_mcp23xxx(config): return var +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value + + CONF_MCP23XXX = "mcp23xxx" -MCP23XXX_OUTPUT_PIN_SCHEMA = cv.Schema( +MCP23XXX_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(MCP23XXXGPIOPin), cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( - MCP23XXX_INTERRUPT_MODES, upper=True - ), - } -) -MCP23XXX_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( @@ -76,42 +82,31 @@ MCP23XXX_INPUT_PIN_SCHEMA = cv.Schema( ) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_MCP23XXX, (MCP23XXX_OUTPUT_PIN_SCHEMA, MCP23XXX_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23XXX, MCP23XXX_PIN_SCHEMA) async def mcp23xxx_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_MCP23XXX]) - return MCP23XXXGPIOPin.new( - parent, - config[CONF_NUMBER], - config[CONF_MODE], - config[CONF_INVERTED], - config[CONF_INTERRUPT], - ) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add(var.set_interrupt_mode(config[CONF_INTERRUPT])) + return var # BEGIN Removed pin schemas below to show error in configuration # TODO remove in 2022.5.0 for id in ["mcp23008", "mcp23s08", "mcp23017", "mcp23s17"]: - PIN_SCHEMA = cv.Schema( - { - cv.Required(id): cv.invalid( - f"'{id}:' has been removed from the pin schema in 1.17.0, please use 'mcp23xxx:'" - ), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum( - MCP23XXX_GPIO_MODES, upper=True - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( - MCP23XXX_INTERRUPT_MODES, upper=True - ), - } + invalid_schema = cv.invalid( + f"'{id}:' has been removed from the pin schema in 1.17.0, please use 'mcp23xxx:'" ) # pylint: disable=cell-var-from-loop - @pins.PIN_SCHEMA_REGISTRY.register(id, (PIN_SCHEMA, PIN_SCHEMA)) + @pins.PIN_SCHEMA_REGISTRY.register(id, invalid_schema) def pin_to_code(config): pass diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 37c55fceaf..14a703fb9f 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -6,16 +6,15 @@ namespace mcp23xxx_base { float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } -MCP23XXXGPIOPin::MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted, - MCP23XXXInterruptMode interrupt_mode) - : GPIOPin(pin, mode, inverted), parent_(parent), interrupt_mode_(interrupt_mode) {} -void MCP23XXXGPIOPin::setup() { this->pin_mode(this->mode_); } -void MCP23XXXGPIOPin::pin_mode(uint8_t mode) { - this->parent_->pin_mode(this->pin_, mode); - this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); -} +void MCP23XXXGPIOPin::setup() { pin_mode(flags_); } +void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string MCP23XXXGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23XXX", pin_); + return buffer; +} } // namespace mcp23xxx_base } // namespace esphome diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index bf01320264..a522ea28c5 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -1,25 +1,18 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace mcp23xxx_base { enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING }; -/// Modes for MCP23XXX pins -enum MCP23XXXGPIOMode : uint8_t { - MCP23XXX_INPUT = INPUT, // 0x00 - MCP23XXX_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - MCP23XXX_OUTPUT = OUTPUT // 0x01 -}; - class MCP23XXXBase : public Component { public: virtual bool digital_read(uint8_t pin); virtual void digital_write(uint8_t pin, bool value); - virtual void pin_mode(uint8_t pin, uint8_t mode); + virtual void pin_mode(uint8_t pin, gpio::Flags flags); virtual void pin_interrupt_mode(uint8_t pin, MCP23XXXInterruptMode interrupt_mode); void set_open_drain_ints(const bool value) { this->open_drain_ints_ = value; } @@ -38,16 +31,23 @@ class MCP23XXXBase : public Component { class MCP23XXXGPIOPin : public GPIOPin { public: - MCP23XXXGPIOPin(MCP23XXXBase *parent, uint8_t pin, uint8_t mode, bool inverted = false, - MCP23XXXInterruptMode interrupt_mode = MCP23XXX_NO_INTERRUPT); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(MCP23XXXBase *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + void set_interrupt_mode(MCP23XXXInterruptMode interrupt_mode) { interrupt_mode_ = interrupt_mode; } protected: MCP23XXXBase *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; MCP23XXXInterruptMode interrupt_mode_; }; diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 16f1c14fcb..6f3dc576ea 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" diff --git a/esphome/components/mcp4725/mcp4725.cpp b/esphome/components/mcp4725/mcp4725.cpp index a8b130208e..2cb19282b6 100644 --- a/esphome/components/mcp4725/mcp4725.cpp +++ b/esphome/components/mcp4725/mcp4725.cpp @@ -8,13 +8,10 @@ static const char *const TAG = "mcp4725"; void MCP4725::setup() { ESP_LOGCONFIG(TAG, "Setting up MCP4725 (0x%02X)...", this->address_); - - this->raw_begin_transmission(); - - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); - return; } } diff --git a/esphome/components/mcp9808/mcp9808.cpp b/esphome/components/mcp9808/mcp9808.cpp index 8f60df1d88..fca1331fc3 100644 --- a/esphome/components/mcp9808/mcp9808.cpp +++ b/esphome/components/mcp9808/mcp9808.cpp @@ -20,14 +20,14 @@ static const char *const TAG = "mcp9808"; void MCP9808Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up %s...", this->name_.c_str()); - uint16_t manu; - if (!this->read_byte_16(MCP9808_REG_MANUF_ID, &manu, 0) || manu != MCP9808_MANUF_ID) { + uint16_t manu = 0; + if (!this->read_byte_16(MCP9808_REG_MANUF_ID, &manu) || manu != MCP9808_MANUF_ID) { this->mark_failed(); ESP_LOGE(TAG, "%s manufacuturer id failed, device returned %X", this->name_.c_str(), manu); return; } - uint16_t dev_id; - if (!this->read_byte_16(MCP9808_REG_DEVICE_ID, &dev_id, 0) || dev_id != MCP9808_DEV_ID) { + uint16_t dev_id = 0; + if (!this->read_byte_16(MCP9808_REG_DEVICE_ID, &dev_id) || dev_id != MCP9808_DEV_ID) { this->mark_failed(); ESP_LOGE(TAG, "%s device id failed, device returned %X", this->name_.c_str(), dev_id); return; @@ -66,7 +66,7 @@ void MCP9808Sensor::update() { temp = (uint16_t)(msb) *16 + lsb / 16.0f; } - if (isnan(temp)) { + if (std::isnan(temp)) { this->status_set_warning(); return; } diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py new file mode 100644 index 0000000000..ec568ae2d5 --- /dev/null +++ b/esphome/components/mdns/__init__.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.core import CORE + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] + +CONF_DISABLED = "disabled" +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_DISABLED, default=False): cv.boolean, + } +) + + +async def to_code(config): + if config[CONF_DISABLED]: + return + + cg.add_define("USE_MDNS") + if CORE.using_arduino: + if CORE.is_esp32: + cg.add_library("ESPmDNS", None) + elif CORE.is_esp8266: + cg.add_library("ESP8266mDNS", None) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp new file mode 100644 index 0000000000..c742fe3948 --- /dev/null +++ b/esphome/components/mdns/mdns_component.cpp @@ -0,0 +1,74 @@ +#include "mdns_component.h" +#include "esphome/core/defines.h" +#include "esphome/core/version.h" +#include "esphome/core/application.h" + +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + +namespace esphome { +namespace mdns { + +#ifndef WEBSERVER_PORT +#define WEBSERVER_PORT 80 // NOLINT +#endif + +std::vector MDNSComponent::compile_services_() { + std::vector res; + +#ifdef USE_API + if (api::global_api_server != nullptr) { + MDNSService service{}; + service.service_type = "esphomelib"; + service.proto = "_tcp"; + service.port = api::global_api_server->get_port(); + service.txt_records.push_back({"version", ESPHOME_VERSION}); + service.txt_records.push_back({"mac", get_mac_address()}); + const char *platform = nullptr; +#ifdef USE_ESP8266 + platform = "ESP8266"; +#endif +#ifdef USE_ESP32 + platform = "ESP32"; +#endif + if (platform != nullptr) { + service.txt_records.push_back({"platform", platform}); + } + + service.txt_records.push_back({"board", ESPHOME_BOARD}); + +#ifdef ESPHOME_PROJECT_NAME + service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME}); + service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION}); +#endif // ESPHOME_PROJECT_NAME + res.push_back(service); + } +#endif // USE_API + +#ifdef USE_PROMETHEUS + { + MDNSService service{}; + service.service_type = "prometheus-http"; + service.proto = "_tcp"; + service.port = WEBSERVER_PORT; + res.push_back(service); + } +#endif + + if (res.empty()) { + // Publish "http" service if not using native API + // This is just to have *some* mDNS service so that .local resolution works + MDNSService service{}; + service.service_type = "http"; + service.proto = "_tcp"; + service.port = WEBSERVER_PORT; + service.txt_records.push_back({"version", ESPHOME_VERSION}); + res.push_back(service); + } + return res; +} +std::string MDNSComponent::compile_hostname_() { return App.get_name(); } + +} // namespace mdns +} // namespace esphome diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h new file mode 100644 index 0000000000..985947d99c --- /dev/null +++ b/esphome/components/mdns/mdns_component.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" + +namespace esphome { +namespace mdns { + +struct MDNSTXTRecord { + std::string key; + std::string value; +}; + +struct MDNSService { + std::string service_type; + std::string proto; + uint16_t port; + std::vector txt_records; +}; + +class MDNSComponent : public Component { + public: + void setup() override; + +#if defined(USE_ESP8266) && defined(USE_ARDUINO) + void loop() override; +#endif + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + protected: + std::vector compile_services_(); + std::string compile_hostname_(); +}; + +} // namespace mdns +} // namespace esphome diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp new file mode 100644 index 0000000000..4d13b7321a --- /dev/null +++ b/esphome/components/mdns/mdns_esp32_arduino.cpp @@ -0,0 +1,27 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + MDNS.begin(compile_hostname_().c_str()); + + auto services = compile_services_(); + for (const auto &service : services) { + MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + } + } +} + +} // namespace mdns +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp new file mode 100644 index 0000000000..48f31f1bbf --- /dev/null +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -0,0 +1,32 @@ +#if defined(USE_ESP8266) && defined(USE_ARDUINO) + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + network::IPAddress addr = network::get_ip_address(); + MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr); + + auto services = compile_services_(); + for (const auto &service : services) { + MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + } + } +} + +void MDNSComponent::loop() { MDNS.update(); } + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp new file mode 100644 index 0000000000..17874f1ffe --- /dev/null +++ b/esphome/components/mdns/mdns_esp_idf.cpp @@ -0,0 +1,52 @@ +#ifdef USE_ESP_IDF + +#include "mdns_component.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace mdns { + +static const char *const TAG = "mdns"; + +void MDNSComponent::setup() { + esp_err_t err = mdns_init(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + mdns_hostname_set(compile_hostname_().c_str()); + mdns_instance_name_set(compile_hostname_().c_str()); + + auto services = compile_services_(); + for (const auto &service : services) { + std::vector txt_records; + for (const auto &record : service.txt_records) { + mdns_txt_item_t it{}; + // dup strings to ensure the pointer is valid even after the record loop + it.key = strdup(record.key.c_str()); + it.value = strdup(record.value.c_str()); + txt_records.push_back(it); + } + err = mdns_service_add(nullptr, service.service_type.c_str(), service.proto.c_str(), service.port, + txt_records.data(), txt_records.size()); + + // free records + for (const auto &it : txt_records) { + delete it.key; // NOLINT(cppcoreguidelines-owning-memory) + delete it.value; // NOLINT(cppcoreguidelines-owning-memory) + } + + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to register mDNS service %s: %s", service.service_type.c_str(), esp_err_to_name(err)); + } + } +} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/adapter.cpp index bd5b289095..a3f19dbda8 100644 --- a/esphome/components/midea/adapter.cpp +++ b/esphome/components/midea/adapter.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "adapter.h" @@ -171,3 +173,5 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea:: } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/adapter.h index 8d8d57e8f9..2497cbbe5b 100644 --- a/esphome/components/midea/adapter.h +++ b/esphome/components/midea/adapter.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include #include "esphome/components/climate/climate_traits.h" #include "appliance_base.h" @@ -40,3 +43,5 @@ class Converters { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index a71f1dbdfb..103b852936 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "esphome/core/log.h" #include "air_conditioner.h" #include "adapter.h" @@ -150,3 +152,5 @@ void AirConditioner::do_display_toggle() { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index 895b6412f3..8dfb9dcb3d 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include #include "appliance_base.h" #include "esphome/components/sensor/sensor.h" @@ -39,3 +42,5 @@ class AirConditioner : public ApplianceBase } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index aa616ced36..43dd2ffb32 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/log.h" #include "esphome/components/uart/uart.h" @@ -19,7 +22,8 @@ using climate::ClimateMode; using climate::ClimateSwingMode; using climate::ClimateFanMode; -template class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate { +template +class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream { static_assert(std::is_base_of::value, "T must derive from dudanov::midea::ApplianceBase class"); @@ -60,6 +64,12 @@ template class ApplianceBase : public Component, public uart::UARTDe } #endif + int available() override { return uart::UARTDevice::available(); } + int read() override { return uart::UARTDevice::read(); } + int peek() override { return uart::UARTDevice::peek(); } + void flush() override { uart::UARTDevice::flush(); } + size_t write(uint8_t data) override { return uart::UARTDevice::write(data); } + protected: T base_; std::set supported_modes_{}; @@ -74,3 +84,5 @@ template class ApplianceBase : public Component, public uart::UARTDe } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/automations.h index 1f026c0c15..5b638286ac 100644 --- a/esphome/components/midea/automations.h +++ b/esphome/components/midea/automations.h @@ -1,4 +1,7 @@ #pragma once + +#ifdef USE_ARDUINO + #include "esphome/core/automation.h" #include "air_conditioner.h" @@ -54,3 +57,5 @@ template class PowerOffAction : public MideaActionBase { } // namespace midea } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 137fcdd607..0d0bdce471 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -151,7 +151,8 @@ CONFIG_SCHEMA = cv.All( } ) .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, ) # Actions diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/midea_ir.h index 2459d844a1..abd4324bcc 100644 --- a/esphome/components/midea/midea_ir.h +++ b/esphome/components/midea/midea_ir.h @@ -1,4 +1,6 @@ #pragma once + +#ifdef USE_ARDUINO #ifdef USE_REMOTE_TRANSMITTER #include "esphome/components/remote_base/midea_protocol.h" @@ -40,3 +42,4 @@ class IrSpecialData : public IrData { } // namespace esphome #endif +#endif // USE_ARDUINO diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 274ed6dfec..7ba3da7b4d 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -1,5 +1,6 @@ #include "mpr121.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace mpr121 { diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 73ee50ee65..8f02f8d437 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -192,6 +192,7 @@ CONFIG_SCHEMA = cv.All( } ), validate_config, + cv.only_with_arduino, ) diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 9dec9498ad..787cc1153f 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -1,4 +1,7 @@ #include "custom_mqtt_device.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" namespace esphome { @@ -28,3 +31,5 @@ bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 1c8b2e916e..9795d69304 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -1,5 +1,8 @@ #pragma once +#include "esphome/core/defines.h" +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "mqtt_client.h" @@ -215,3 +218,5 @@ void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callba } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 53a49c6844..d7322298bb 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_binary_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_BINARY_SENSOR namespace esphome { @@ -55,3 +56,4 @@ bool MQTTBinarySensorComponent::publish_state(bool state) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index 1ca82a947e..c459bea1f7 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/defines.h" - +#ifdef USE_MQTT #ifdef USE_BINARY_SENSOR #include "mqtt_component.h" @@ -41,3 +41,4 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 237a8146e6..040b0001fe 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -1,9 +1,11 @@ #include "mqtt_client.h" +#ifdef USE_MQTT + #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -60,7 +62,7 @@ void MQTTClientComponent::setup() { void MQTTClientComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT:"); ESP_LOGCONFIG(TAG, " Server Address: %s:%u (%s)", this->credentials_.address.c_str(), this->credentials_.port, - this->ip_.toString().c_str()); + this->ip_.str().c_str()); ESP_LOGCONFIG(TAG, " Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); ESP_LOGCONFIG(TAG, " Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); if (!this->discovery_info_.prefix.empty()) { @@ -87,11 +89,11 @@ void MQTTClientComponent::start_dnslookup_() { this->dns_resolve_error_ = false; this->dns_resolved_ = false; ip_addr_t addr; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, esphome::mqtt::MQTTClientComponent::dns_found_callback, this); #endif @@ -99,11 +101,11 @@ void MQTTClientComponent::start_dnslookup_() { case ERR_OK: { // Got IP immediately this->dns_resolved_ = true; -#ifdef ARDUINO_ARCH_ESP32 - this->ip_ = IPAddress(addr.u_addr.ip4.addr); +#ifdef USE_ESP32 + this->ip_ = addr.u_addr.ip4.addr; #endif -#ifdef ARDUINO_ARCH_ESP8266 - this->ip_ = IPAddress(addr.addr); +#ifdef USE_ESP8266 + this->ip_ = addr.addr; #endif this->start_connect_(); return; @@ -116,7 +118,7 @@ void MQTTClientComponent::start_dnslookup_() { default: case ERR_ARG: { // error -#if defined(ARDUINO_ARCH_ESP8266) +#if defined(USE_ESP8266) ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %ld", err); #else ESP_LOGW(TAG, "Error resolving MQTT broker IP address: %d", err); @@ -143,10 +145,10 @@ void MQTTClientComponent::check_dnslookup_() { return; } - ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.toString().c_str()); + ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str()); this->start_connect_(); } -#if defined(ARDUINO_ARCH_ESP8266) && LWIP_VERSION_MAJOR == 1 +#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 void MQTTClientComponent::dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg) { #else void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) { @@ -155,18 +157,18 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t * if (ipaddr == nullptr) { a_this->dns_resolve_error_ = true; } else { -#ifdef ARDUINO_ARCH_ESP32 - a_this->ip_ = IPAddress(ipaddr->u_addr.ip4.addr); +#ifdef USE_ESP32 + a_this->ip_ = ipaddr->u_addr.ip4.addr; #endif -#ifdef ARDUINO_ARCH_ESP8266 - a_this->ip_ = IPAddress(ipaddr->addr); +#ifdef USE_ESP8266 + a_this->ip_ = ipaddr->addr; #endif a_this->dns_resolved_ = true; } } void MQTTClientComponent::start_connect_() { - if (!network_is_connected()) + if (!network::is_connected()) return; ESP_LOGI(TAG, "Connecting to MQTT..."); @@ -183,7 +185,7 @@ void MQTTClientComponent::start_connect_() { this->mqtt_client_.setCredentials(username, password); - this->mqtt_client_.setServer(this->ip_, this->credentials_.port); + this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port); if (!this->last_will_.topic.empty()) { this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, this->last_will_.payload.c_str(), this->last_will_.payload.length()); @@ -251,7 +253,7 @@ void MQTTClientComponent::loop() { reason_s = LOG_STR("Unknown"); break; } - if (!network_is_connected()) { + if (!network::is_connected()) { reason_s = LOG_STR("WiFi disconnected"); } ESP_LOGW(TAG, "MQTT Disconnected: %s.", LOG_STR_ARG(reason_s)); @@ -477,7 +479,7 @@ static bool topic_match(const char *message, const char *subscription) { } void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // on ESP8266, this is called in LWiP thread; some components do not like running // in an ISR. this->defer([this, topic, payload]() { @@ -485,7 +487,7 @@ void MQTTClientComponent::on_message(const std::string &topic, const std::string for (auto &subscription : this->subscriptions_) if (topic_match(topic.c_str(), subscription.topic.c_str())) subscription.callback(topic, payload); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 }); #endif } @@ -587,3 +589,5 @@ float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AF } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 119e61db2b..fa689eaa04 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -1,10 +1,14 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/defines.h" + +#ifdef USE_MQTT + +#include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/log.h" #include "esphome/components/json/json_util.h" +#include "esphome/components/network/ip_address.h" #include #include "lwip/ip_addr.h" @@ -226,7 +230,7 @@ class MQTTClientComponent : public Component { void start_connect_(); void start_dnslookup_(); void check_dnslookup_(); -#if defined(ARDUINO_ARCH_ESP8266) && LWIP_VERSION_MAJOR == 1 +#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 static void dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg); #else static void dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg); @@ -265,7 +269,7 @@ class MQTTClientComponent : public Component { std::vector subscriptions_; AsyncMqttClient mqtt_client_; MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; - IPAddress ip_; + network::IPAddress ip_; bool dns_resolved_{false}; bool dns_resolve_error_{false}; std::vector children_; @@ -352,3 +356,5 @@ template class MQTTConnectedCondition : public Condition } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index be9dbb0a08..8519e296b0 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,6 +1,7 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_CLIMATE namespace esphome { @@ -245,7 +246,7 @@ bool MQTTClimateComponent::publish_state_() { if (!this->publish(this->get_mode_state_topic(), mode_s)) success = false; int8_t accuracy = traits.get_temperature_accuracy_decimals(); - if (traits.get_supports_current_temperature() && !isnan(this->device_->current_temperature)) { + if (traits.get_supports_current_temperature() && !std::isnan(this->device_->current_temperature)) { std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy); if (!this->publish(this->get_current_temperature_state_topic(), payload)) success = false; @@ -359,3 +360,4 @@ bool MQTTClimateComponent::publish_state_() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 8aea4feb26..8b8a8e866e 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" @@ -48,3 +49,4 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 3ed9aafb42..96cda57914 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -1,4 +1,7 @@ #include "mqtt_component.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -198,3 +201,5 @@ bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connec } // namespace mqtt } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 668162da5a..f07e752e02 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include #include "esphome/core/component.h" @@ -181,3 +185,5 @@ class MQTTComponent : public Component { } // namespace mqtt } // namespace esphome + +#endif // USE_MQTt diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index b11ae1fb93..6a0d2d1bc5 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,6 +1,7 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_COVER namespace esphome { @@ -115,3 +116,4 @@ bool MQTTCoverComponent::publish_state() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index 5c2ce93987..b8c9f2617c 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -3,6 +3,7 @@ #include "esphome/core/defines.h" #include "mqtt_component.h" +#ifdef USE_MQTT #ifdef USE_COVER #include "esphome/components/cover/cover.h" @@ -40,3 +41,4 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index b8eecf0ff3..c8db5ecece 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,6 +1,7 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_helpers.h" @@ -127,3 +128,4 @@ bool MQTTFanComponent::publish_state() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 5495780a27..99d9c055cf 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_state.h" @@ -45,3 +46,4 @@ class MQTTFanComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index f53be9c010..d028b1b037 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,6 +1,7 @@ #include "mqtt_light.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_LIGHT #include "esphome/components/light/light_json_schema.h" @@ -79,3 +80,4 @@ void MQTTJSONLightComponent::dump_config() { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index 9417e71ddd..b0f3145900 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_LIGHT #include "mqtt_component.h" @@ -39,3 +40,4 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index f209f4fe20..faa38056a9 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -1,6 +1,7 @@ #include "mqtt_number.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_NUMBER namespace esphome { @@ -63,3 +64,4 @@ bool MQTTNumberComponent::publish_state(float value) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index f44de91435..46dc221e9e 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_NUMBER #include "esphome/components/number/number.h" @@ -44,3 +45,4 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index c0ac472d46..467a0cd84c 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -1,6 +1,7 @@ #include "mqtt_select.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SELECT namespace esphome { @@ -56,3 +57,4 @@ bool MQTTSelectComponent::publish_state(const std::string &value) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index 013e905ead..0115c0a41e 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SELECT #include "esphome/components/select/select.h" @@ -44,3 +45,4 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index e921056167..72ec7c54ee 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SENSOR #ifdef USE_DEEP_SLEEP @@ -77,3 +78,4 @@ std::string MQTTSensorComponent::unique_id() { return this->sensor_->unique_id() } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 8d8fa83531..2385529f4f 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" @@ -58,3 +59,4 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b73e1ab8dc..b13ddd5d9d 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -1,6 +1,7 @@ #include "mqtt_switch.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_SWITCH namespace esphome { @@ -58,3 +59,4 @@ bool MQTTSwitchComponent::publish_state(bool state) { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index 33b829c856..9959d21872 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" @@ -39,3 +40,4 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 8bc11d954c..fd96cd0902 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -1,6 +1,7 @@ #include "mqtt_text_sensor.h" #include "esphome/core/log.h" +#ifdef USE_MQTT #ifdef USE_TEXT_SENSOR namespace esphome { @@ -43,3 +44,4 @@ std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); } } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index a5ce0658c7..1d3f95b894 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" +#ifdef USE_MQTT #ifdef USE_TEXT_SENSOR #include "esphome/components/text_sensor/text_sensor.h" @@ -40,3 +41,4 @@ class MQTTTextSensor : public mqtt::MQTTComponent { } // namespace esphome #endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index 06251bd7c8..e1accf3c70 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -1,4 +1,7 @@ #include "mqtt_subscribe_sensor.h" + +#ifdef USE_MQTT + #include "esphome/core/log.h" namespace esphome { @@ -31,3 +34,5 @@ void MQTTSubscribeSensor::dump_config() { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h index a303ccad89..0619326ac9 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/mqtt/mqtt_client.h" @@ -25,3 +29,5 @@ class MQTTSubscribeSensor : public sensor::Sensor, public Component { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp index 2b0908979c..8aa094a2d4 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp +++ b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp @@ -1,5 +1,7 @@ #include "mqtt_subscribe_text_sensor.h" +#ifdef USE_MQTT + #include "esphome/core/log.h" #include @@ -22,3 +24,5 @@ void MQTTSubscribeTextSensor::dump_config() { } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h index 69409f6348..9f8e5c63cc 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h +++ b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.h @@ -1,5 +1,9 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + #include "esphome/core/component.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/mqtt/mqtt_client.h" @@ -24,3 +28,5 @@ class MQTTSubscribeTextSensor : public text_sensor::TextSensor, public Component } // namespace mqtt_subscribe } // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp index 51dc569240..1d7516dbe8 100644 --- a/esphome/components/ms5611/ms5611.cpp +++ b/esphome/components/ms5611/ms5611.cpp @@ -1,5 +1,6 @@ #include "ms5611.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ms5611 { diff --git a/esphome/components/my9231/my9231.h b/esphome/components/my9231/my9231.h index ee15f9743c..a777dcc960 100644 --- a/esphome/components/my9231/my9231.h +++ b/esphome/components/my9231/my9231.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace my9231 { diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index a86b4e4588..0117f1b063 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -168,14 +168,15 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, cv.Optional(CONF_METHOD, default=None): validate_method, cv.Optional(CONF_INVERT, default="no"): cv.boolean, - cv.Optional(CONF_PIN): pins.output_pin, - cv.Optional(CONF_CLOCK_PIN): pins.output_pin, - cv.Optional(CONF_DATA_PIN): pins.output_pin, + cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_DATA_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, } ).extend(cv.COMPONENT_SCHEMA), _validate, validate_method_pin, + cv.only_with_arduino, ) diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 5359bac61b..34e10f2cfe 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -7,7 +9,7 @@ #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) #error The NeoPixelBus library requires at least arduino_version 2.4.x #endif @@ -144,3 +146,5 @@ class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase +#include +#include +#include + +namespace esphome { +namespace network { + +struct IPAddress { + public: + IPAddress() : addr_({0, 0, 0, 0}) {} + IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) : addr_({first, second, third, fourth}) {} + IPAddress(uint32_t raw) { + addr_[0] = (uint8_t)(raw >> 0); + addr_[1] = (uint8_t)(raw >> 8); + addr_[2] = (uint8_t)(raw >> 16); + addr_[3] = (uint8_t)(raw >> 24); + } + operator uint32_t() const { + uint32_t res = 0; + res |= ((uint32_t) addr_[0]) << 0; + res |= ((uint32_t) addr_[1]) << 8; + res |= ((uint32_t) addr_[2]) << 16; + res |= ((uint32_t) addr_[3]) << 24; + return res; + } + std::string str() const { + char buffer[24]; + snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", addr_[0], addr_[1], addr_[2], addr_[3]); + return buffer; + } + bool operator==(const IPAddress &other) const { + return addr_[0] == other.addr_[0] && addr_[1] == other.addr_[1] && addr_[2] == other.addr_[2] && + addr_[3] == other.addr_[3]; + } + uint8_t operator[](int index) const { return addr_[index]; } + uint8_t &operator[](int index) { return addr_[index]; } + + protected: + std::array addr_; +}; + +} // namespace network +} // namespace esphome diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp new file mode 100644 index 0000000000..f7ac6b543e --- /dev/null +++ b/esphome/components/network/util.cpp @@ -0,0 +1,54 @@ +#include "util.h" +#include "esphome/core/defines.h" + +#ifdef USE_WIFI +#include "esphome/components/wifi/wifi_component.h" +#endif + +#ifdef USE_ETHERNET +#include "esphome/components/ethernet/ethernet_component.h" +#endif + +namespace esphome { +namespace network { + +bool is_connected() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr && ethernet::global_eth_component->is_connected()) + return true; +#endif + +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->is_connected(); +#endif + + return false; +} + +network::IPAddress get_ip_address() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr) + return ethernet::global_eth_component->get_ip_address(); +#endif +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->get_ip_address(); +#endif + return {}; +} + +std::string get_use_address() { +#ifdef USE_ETHERNET + if (ethernet::global_eth_component != nullptr) + return ethernet::global_eth_component->get_use_address(); +#endif +#ifdef USE_WIFI + if (wifi::global_wifi_component != nullptr) + return wifi::global_wifi_component->get_use_address(); +#endif + return ""; +} + +} // namespace network +} // namespace esphome diff --git a/esphome/components/network/util.h b/esphome/components/network/util.h new file mode 100644 index 0000000000..f248d5cbf4 --- /dev/null +++ b/esphome/components/network/util.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "ip_address.h" + +namespace esphome { +namespace network { + +/// Return whether the node is connected to the network (through wifi, eth, ...) +bool is_connected(); +/// Get the active network hostname +std::string get_use_address(); +IPAddress get_ip_address(); + +} // namespace network +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index e693b2f1ec..f4b35fd56f 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -33,7 +33,7 @@ CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), - cv.Optional(CONF_TFT_URL): cv.string, + cv.Optional(CONF_TFT_URL): cv.All(cv.string, cv.only_with_arduino), cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_ON_SETUP): automation.validate_automation( { @@ -74,7 +74,7 @@ async def to_code(config): cg.add(var.set_writer(lambda_)) if CONF_TFT_URL in config: - cg.add_define("USE_TFT_UPLOAD") + cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) if CONF_TOUCH_SLEEP_TIMEOUT in config: diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 45c4a629c4..1bee41f6cf 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -7,11 +7,11 @@ #include "nextion_component.h" #include "esphome/components/display/display_color_utils.h" -#if defined(USE_ETHERNET) || defined(USE_WIFI) -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -652,7 +652,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); -#ifdef USE_TFT_UPLOAD +#ifdef USE_NEXTION_TFT_UPLOAD /** * Set the tft file URL. https seems problamtic with arduino.. */ @@ -770,9 +770,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe const std::string &variable_name_to_send, const std::string &state_value, bool is_sleep_safe = false); -#ifdef USE_TFT_UPLOAD -#if defined(USE_ETHERNET) || defined(USE_WIFI) -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_NEXTION_TFT_UPLOAD +#ifdef USE_ESP8266 WiFiClient *wifi_client_{nullptr}; BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; WiFiClient *get_wifi_client_(); @@ -801,9 +800,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); void upload_end_(); -#endif - -#endif +#endif // USE_NEXTION_TFT_UPLOAD bool get_is_connected_() { return this->is_connected_; } @@ -828,7 +825,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void remove_front_no_sensors_(); -#ifdef USE_TFT_UPLOAD +#ifdef USE_NEXTION_TFT_UPLOAD std::string tft_url_; uint8_t *transfer_buffer_{nullptr}; size_t transfer_buffer_size_; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index f864a397cc..9a748277d8 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -1,16 +1,16 @@ +#ifdef USE_NEXTION_TFT_UPLOAD #include "nextion.h" #include "esphome/core/application.h" #include "esphome/core/macros.h" #include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/components/network/util.h" namespace esphome { namespace nextion { static const char *const TAG = "nextion_upload"; -#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) - // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -26,7 +26,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { if (range_end > this->tft_size_) range_end = this->tft_size_; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -46,10 +46,10 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { int code = 0; bool begin_status = false; while (tries <= 5) { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = http->begin(this->tft_url_.c_str()); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); #endif @@ -129,7 +129,7 @@ void Nextion::upload_tft() { return; } - if (!network_is_connected()) { + if (!network::is_connected()) { ESP_LOGD(TAG, "network is not connected"); return; } @@ -139,10 +139,10 @@ void Nextion::upload_tft() { HTTPClient http; http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along bool begin_status = false; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 begin_status = http.begin(this->tft_url_.c_str()); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) @@ -157,7 +157,7 @@ void Nextion::upload_tft() { if (!begin_status) { this->is_updating_ = false; ESP_LOGD(TAG, "connection failed"); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (psramFound()) free(this->transfer_buffer_); // NOLINT else @@ -249,7 +249,7 @@ void Nextion::upload_tft() { } // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 uint32_t chunk_size = 8192; if (psramFound()) { chunk_size = this->content_length_; @@ -268,7 +268,7 @@ void Nextion::upload_tft() { #endif if (this->transfer_buffer_ == nullptr) { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (psramFound()) { ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); @@ -289,7 +289,7 @@ void Nextion::upload_tft() { if (!this->transfer_buffer_) this->upload_end_(); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 } #endif } @@ -325,7 +325,7 @@ void Nextion::upload_end_() { ESP.restart(); // NOLINT(readability-static-accessed-through-instance) } -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { @@ -342,9 +342,7 @@ WiFiClient *Nextion::get_wifi_client_() { return this->wifi_client_; } #endif - -#else -void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } -#endif } // namespace nextion } // namespace esphome + +#endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index 1e79164546..e983ebcc6f 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -48,7 +48,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { if (!this->nextion_->is_setup()) return; - if (isnan(state)) + if (std::isnan(state)) return; if (this->wave_chan_id_ == UINT8_MAX) { diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp index 80a11384b9..333dbc5a75 100644 --- a/esphome/components/ntc/ntc.cpp +++ b/esphome/components/ntc/ntc.cpp @@ -14,7 +14,7 @@ void NTC::setup() { void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) } float NTC::get_setup_priority() const { return setup_priority::DATA; } void NTC::process_(float value) { - if (isnan(value)) { + if (std::isnan(value)) { this->publish_state(NAN); return; } diff --git a/esphome/components/number/automation.cpp b/esphome/components/number/automation.cpp index a0b169427f..c75d272660 100644 --- a/esphome/components/number/automation.cpp +++ b/esphome/components/number/automation.cpp @@ -7,7 +7,7 @@ namespace number { static const char *const TAG = "number.automation"; void ValueRangeTrigger::setup() { - this->rtc_ = global_preferences.make_preference(this->parent_->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->parent_->get_object_id_hash()); bool initial_state; if (this->rtc_.load(&initial_state)) { this->previous_in_range_ = initial_state; @@ -18,18 +18,18 @@ void ValueRangeTrigger::setup() { float ValueRangeTrigger::get_setup_priority() const { return setup_priority::HARDWARE; } void ValueRangeTrigger::on_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; float local_min = this->min_.value(state); float local_max = this->max_.value(state); bool in_range; - if (isnan(local_min) && isnan(local_max)) { + if (std::isnan(local_min) && std::isnan(local_max)) { in_range = this->previous_in_range_; - } else if (isnan(local_min)) { + } else if (std::isnan(local_min)) { in_range = state <= local_max; - } else if (isnan(local_max)) { + } else if (std::isnan(local_max)) { in_range = state >= local_min; } else { in_range = local_min <= state && state <= local_max; diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h index 9e812f8c49..98554a346a 100644 --- a/esphome/components/number/automation.h +++ b/esphome/components/number/automation.h @@ -57,9 +57,9 @@ template class NumberInRangeCondition : public Condition void set_max(float max) { this->max_ = max; } bool check(Ts... x) override { const float state = this->parent_->state; - if (isnan(this->min_)) { + if (std::isnan(this->min_)) { return state <= this->max_; - } else if (isnan(this->max_)) { + } else if (std::isnan(this->max_)) { return state >= this->min_; } else { return this->min_ <= state && state <= this->max_; diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index dbc1c88a5d..57a5c7c4bd 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,7 +8,7 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (!this->value_.has_value() || isnan(*this->value_)) { + if (!this->value_.has_value() || std::isnan(*this->value_)) { ESP_LOGW(TAG, "No value set for NumberCall"); return; } diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 75641ad399..59ab22056b 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -15,6 +15,7 @@ from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +AUTO_LOAD = ["socket"] CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_BEGIN = "on_begin" @@ -33,12 +34,21 @@ OTAProgressTrigger = ota_ns.class_("OTAProgressTrigger", automation.Trigger.temp OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) + +def validate_password_support(value): + if CORE.using_arduino: + return value + if CORE.using_esp_idf: + raise cv.Invalid("Password support is not implemented yet for ESP-IDF") + raise NotImplementedError + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, - cv.Optional(CONF_PASSWORD, default=""): cv.string, + cv.Optional(CONF_PASSWORD): cv.All(cv.string, validate_password_support), cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, @@ -76,7 +86,9 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_port(config[CONF_PORT])) - cg.add(var.set_auth_password(config[CONF_PASSWORD])) + if CONF_PASSWORD in config: + cg.add(var.set_auth_password(config[CONF_PASSWORD])) + cg.add_define("USE_OTA_PASSWORD") await cg.register_component(var, config) @@ -88,7 +100,7 @@ async def to_code(config): if CORE.is_esp8266: cg.add_library("Update", None) - elif CORE.is_esp32: + elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("Hash", None) use_state_callback = False diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index e8ac1b9bcd..7ee3ed2866 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -2,14 +2,31 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" +#include "esphome/components/network/util.h" +#include #include + +#ifdef USE_ARDUINO +#ifdef USE_OTA_PASSWORD #include -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_OTA_PASSWORD + +#ifdef USE_ESP32 #include +#endif // USE_ESP32 +#endif // USE_ARDUINO + +#ifdef USE_ESP8266 +#include +#include "esphome/components/esp8266/preferences.h" +#endif // USE_ESP8266 + +#ifdef USE_ESP_IDF +#include #endif -#include namespace esphome { namespace ota { @@ -18,19 +35,177 @@ static const char *const TAG = "ota"; static const uint8_t OTA_VERSION_1_0 = 1; +class OTABackend { + public: + virtual ~OTABackend() = default; + virtual OTAResponseTypes begin(size_t image_size) = 0; + virtual void set_update_md5(const char *md5) = 0; + virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; + virtual OTAResponseTypes end() = 0; + virtual void abort() = 0; +}; + +#ifdef USE_ARDUINO +class ArduinoOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { +#ifdef USE_ESP8266 + esp8266::preferences_prevent_write(true); +#endif + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); +#ifdef USE_ESP8266 + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; +#endif +#ifdef USE_ESP32 + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; +#endif + return OTA_RESPONSE_ERROR_UNKNOWN; + } + void set_update_md5(const char *md5) override { Update.setMD5(md5); } + OTAResponseTypes write(uint8_t *data, size_t len) override { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; + } + OTAResponseTypes end() override { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; + } + void abort() override { +#ifdef USE_ESP32 + Update.abort(); +#endif + +#ifdef USE_ESP8266 + Update.end(); + esp8266::preferences_prevent_write(false); +#endif + } +}; +std::unique_ptr make_ota_backend() { return make_unique(); } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +class IDFOTABackend : public OTABackend { + public: + esp_ota_handle_t update_handle = 0; + + OTAResponseTypes begin(size_t image_size) override { + const esp_partition_t *update_partition = esp_ota_get_next_update_partition(nullptr); + if (update_partition == nullptr) { + return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; + } + esp_err_t err = esp_ota_begin(update_partition, image_size, &update_handle); + if (err != ESP_OK) { + esp_ota_abort(update_handle); + update_handle = 0; + if (err == ESP_ERR_INVALID_SIZE) { + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + void set_update_md5(const char *md5) override { + // pass + } + OTAResponseTypes write(uint8_t *data, size_t len) override { + esp_err_t err = esp_ota_write(update_handle, data, len); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_MAGIC; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + OTAResponseTypes end() override { + esp_err_t err = esp_ota_end(update_handle); + update_handle = 0; + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_UPDATE_END; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; + } + void abort() override { esp_ota_abort(update_handle); } +}; +std::unique_ptr make_ota_backend() { return make_unique(); } +#endif // USE_ESP_IDF + void OTAComponent::setup() { - this->server_ = make_unique(this->port_); - this->server_->begin(); + server_ = socket::socket(AF_INET, SOCK_STREAM, 0); + if (server_ == nullptr) { + ESP_LOGW(TAG, "Could not create socket."); + this->mark_failed(); + return; + } + int enable = 1; + int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); + // we can still continue + } + err = server_->setblocking(false); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); + this->mark_failed(); + return; + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ESPHOME_INADDR_ANY; + server.sin_port = htons(this->port_); + + err = server_->bind((struct sockaddr *) &server, sizeof(server)); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); + this->mark_failed(); + return; + } + + err = server_->listen(4); + if (err != 0) { + ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); + this->mark_failed(); + return; + } this->dump_config(); } void OTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air Updates:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_); +#ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { ESP_LOGCONFIG(TAG, " Using Password."); } +#endif if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1) { ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %d restarts", this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_); @@ -57,25 +232,31 @@ void OTAComponent::handle_() { char *sbuf = reinterpret_cast(buf); uint32_t ota_size; uint8_t ota_features; + std::unique_ptr backend; (void) ota_features; - if (!this->client_.connected()) { - this->client_ = this->server_->available(); + if (client_ == nullptr) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len); + } + if (client_ == nullptr) + return; - if (!this->client_.connected()) - return; + int enable = 1; + int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket could not enable tcp nodelay, errno: %d", errno); + return; } - // enable nodelay for outgoing data - this->client_.setNoDelay(true); - - ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_.remoteIP().toString().c_str()); + ESP_LOGD(TAG, "Starting OTA Update from %s...", this->client_->getpeername().c_str()); this->status_set_warning(); #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_STARTED, 0.0f, 0); #endif - if (!this->wait_receive_(buf, 5)) { + if (!this->readall_(buf, 5)) { ESP_LOGW(TAG, "Reading magic bytes failed!"); goto error; } @@ -88,11 +269,12 @@ void OTAComponent::handle_() { } // Send OK and version - 2 bytes - this->client_.write(OTA_RESPONSE_OK); - this->client_.write(OTA_VERSION_1_0); + buf[0] = OTA_RESPONSE_OK; + buf[1] = OTA_VERSION_1_0; + this->writeall_(buf, 2); // Read features - 1 byte - if (!this->wait_receive_(buf, 1)) { + if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); goto error; } @@ -100,10 +282,13 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "OTA features is 0x%02X", ota_features); // Acknowledge header - 1 byte - this->client_.write(OTA_RESPONSE_HEADER_OK); + buf[0] = OTA_RESPONSE_HEADER_OK; + this->writeall_(buf, 1); +#ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { - this->client_.write(OTA_RESPONSE_REQUEST_AUTH); + buf[0] = OTA_RESPONSE_REQUEST_AUTH; + this->writeall_(buf, 1); MD5Builder md5_builder{}; md5_builder.begin(); sprintf(sbuf, "%08X", random_uint32()); @@ -113,7 +298,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf); // Send nonce, 32 bytes hex MD5 - if (this->client_.write(reinterpret_cast(sbuf), 32) != 32) { + if (!this->writeall_(reinterpret_cast(sbuf), 32)) { ESP_LOGW(TAG, "Auth: Writing nonce failed!"); goto error; } @@ -125,7 +310,7 @@ void OTAComponent::handle_() { md5_builder.add(sbuf); // Receive cnonce, 32 bytes hex MD5 - if (!this->wait_receive_(buf, 32)) { + if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Auth: Reading cnonce failed!"); goto error; } @@ -140,7 +325,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 - if (!this->wait_receive_(buf + 64, 32)) { + if (!this->writeall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); goto error; } @@ -157,12 +342,14 @@ void OTAComponent::handle_() { goto error; } } +#endif // USE_OTA_PASSWORD // Acknowledge auth OK - 1 byte - this->client_.write(OTA_RESPONSE_AUTH_OK); + buf[0] = OTA_RESPONSE_AUTH_OK; + this->writeall_(buf, 1); // Read size, 4 bytes MSB first - if (!this->wait_receive_(buf, 4)) { + if (!this->readall_(buf, 4)) { ESP_LOGW(TAG, "Reading size failed!"); goto error; } @@ -173,72 +360,46 @@ void OTAComponent::handle_() { } ESP_LOGV(TAG, "OTA size is %u bytes", ota_size); -#ifdef ARDUINO_ARCH_ESP8266 - global_preferences.prevent_write(true); -#endif - - if (!Update.begin(ota_size, U_FLASH)) { - uint8_t error = Update.getError(); - StreamString ss; - Update.printError(ss); -#ifdef ARDUINO_ARCH_ESP8266 - if (error == UPDATE_ERROR_BOOTSTRAP) { - error_code = OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - goto error; - } - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) { - error_code = OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - goto error; - } - if (error == UPDATE_ERROR_FLASH_CONFIG) { - error_code = OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - goto error; - } - if (error == UPDATE_ERROR_SPACE) { - error_code = OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; - goto error; - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (error == UPDATE_ERROR_SIZE) { - error_code = OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; - goto error; - } -#endif - ESP_LOGW(TAG, "Preparing OTA partition failed! '%s'", ss.c_str()); - error_code = OTA_RESPONSE_ERROR_UPDATE_PREPARE; + backend = make_ota_backend(); + error_code = backend->begin(ota_size); + if (error_code != OTA_RESPONSE_OK) goto error; - } update_started = true; // Acknowledge prepare OK - 1 byte - this->client_.write(OTA_RESPONSE_UPDATE_PREPARE_OK); + buf[0] = OTA_RESPONSE_UPDATE_PREPARE_OK; + this->writeall_(buf, 1); // Read binary MD5, 32 bytes - if (!this->wait_receive_(buf, 32)) { + if (!this->readall_(buf, 32)) { ESP_LOGW(TAG, "Reading binary MD5 checksum failed!"); goto error; } sbuf[32] = '\0'; ESP_LOGV(TAG, "Update: Binary MD5 is %s", sbuf); - Update.setMD5(sbuf); + backend->set_update_md5(sbuf); // Acknowledge MD5 OK - 1 byte - this->client_.write(OTA_RESPONSE_BIN_MD5_OK); + buf[0] = OTA_RESPONSE_BIN_MD5_OK; + this->writeall_(buf, 1); - while (!Update.isFinished()) { - size_t available = this->wait_receive_(buf, 0); - if (!available) { + while (total < ota_size) { + // TODO: timeout check + size_t requested = std::min(sizeof(buf), ota_size - total); + ssize_t read = this->client_->read(buf, requested); + if (read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; } - uint32_t written = Update.write(buf, available); - if (written != available) { - ESP_LOGW(TAG, "Error writing binary data to flash: %u != %u!", written, available); // NOLINT - error_code = OTA_RESPONSE_ERROR_WRITING_FLASH; + error_code = backend->write(buf, read); + if (error_code != OTA_RESPONSE_OK) { + ESP_LOGW(TAG, "Error writing binary data to flash!"); goto error; } - total += written; + total += read; uint32_t now = millis(); if (now - last_progress > 1000) { @@ -254,24 +415,27 @@ void OTAComponent::handle_() { } // Acknowledge receive OK - 1 byte - this->client_.write(OTA_RESPONSE_RECEIVE_OK); + buf[0] = OTA_RESPONSE_RECEIVE_OK; + this->writeall_(buf, 1); - if (!Update.end()) { - error_code = OTA_RESPONSE_ERROR_UPDATE_END; + error_code = backend->end(); + if (error_code != OTA_RESPONSE_OK) { + ESP_LOGW(TAG, "Error ending OTA!"); goto error; } // Acknowledge Update end OK - 1 byte - this->client_.write(OTA_RESPONSE_UPDATE_END_OK); + buf[0] = OTA_RESPONSE_UPDATE_END_OK; + this->writeall_(buf, 1); // Read ACK - if (!this->wait_receive_(buf, 1, false) || buf[0] != OTA_RESPONSE_OK) { + if (!this->readall_(buf, 1) || buf[0] != OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Reading back acknowledgement failed!"); // do not go to error, this is not fatal } - this->client_.flush(); - this->client_.stop(); + this->client_->close(); + this->client_ = nullptr; delay(10); ESP_LOGI(TAG, "OTA update finished!"); this->status_clear_warning(); @@ -282,88 +446,72 @@ void OTAComponent::handle_() { App.safe_reboot(); error: - if (update_started) { - StreamString ss; - Update.printError(ss); - ESP_LOGW(TAG, "Update end failed! Error: %s", ss.c_str()); - } - if (this->client_.connected()) { - this->client_.write(static_cast(error_code)); - this->client_.flush(); - } - this->client_.stop(); + buf[0] = static_cast(error_code); + this->writeall_(buf, 1); + this->client_->close(); + this->client_ = nullptr; -#ifdef ARDUINO_ARCH_ESP32 - if (update_started) { - Update.abort(); + if (backend != nullptr && update_started) { + backend->abort(); } -#endif - -#ifdef ARDUINO_ARCH_ESP8266 - if (update_started) { - Update.end(); - } -#endif this->status_momentary_error("onerror", 5000); #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_ERROR, 0.0f, static_cast(error_code)); #endif - -#ifdef ARDUINO_ARCH_ESP8266 - global_preferences.prevent_write(false); -#endif } -size_t OTAComponent::wait_receive_(uint8_t *buf, size_t bytes, bool check_disconnected) { - size_t available = 0; +bool OTAComponent::readall_(uint8_t *buf, size_t len) { uint32_t start = millis(); - do { - App.feed_wdt(); - if (check_disconnected && !this->client_.connected()) { - ESP_LOGW(TAG, "Error client disconnected while receiving data!"); - return 0; - } - int availi = this->client_.available(); - if (availi < 0) { - ESP_LOGW(TAG, "Error reading data!"); - return 0; - } + uint32_t at = 0; + while (len - at > 0) { uint32_t now = millis(); - if (availi == 0 && now - start > 10000) { - ESP_LOGW(TAG, "Timeout waiting for data!"); - return 0; + if (now - start > 1000) { + ESP_LOGW(TAG, "Timed out reading %d bytes of data", len); + return false; } - available = size_t(availi); - yield(); - } while (bytes == 0 ? available == 0 : available < bytes); - if (bytes == 0) - bytes = std::min(available, size_t(1024)); - - bool success = false; - for (uint32_t i = 0; !success && i < 100; i++) { - int res = this->client_.read(buf, bytes); - - if (res != int(bytes)) { - // ESP32 implementation has an issue where calling read can fail with EAGAIN (race condition) - // so just re-try it until it works (with generous timeout of 1s) - // because we check with available() first this should not cause us any trouble in all other cases - delay(10); + ssize_t read = this->client_->read(buf + at, len - at); + if (read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); + continue; + } + ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno); + return false; } else { - success = true; + at += read; } + delay(1); } - if (!success) { - ESP_LOGW(TAG, "Reading %u bytes of binary data failed!", bytes); // NOLINT - return 0; - } - - return bytes; + return true; } +bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { + uint32_t start = millis(); + uint32_t at = 0; + while (len - at > 0) { + uint32_t now = millis(); + if (now - start > 1000) { + ESP_LOGW(TAG, "Timed out writing %d bytes of data", len); + return false; + } -void OTAComponent::set_auth_password(const std::string &password) { this->password_ = password; } + ssize_t written = this->client_->write(buf + at, len - at); + if (written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); + continue; + } + ESP_LOGW(TAG, "Failed to write %d bytes of data, errno: %d", len, errno); + return false; + } else { + at += written; + } + delay(1); + } + return true; +} float OTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } uint16_t OTAComponent::get_port() const { return this->port_; } @@ -373,7 +521,7 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ this->safe_mode_start_time_ = millis(); this->safe_mode_enable_time_ = enable_time; this->safe_mode_num_attempts_ = num_attempts; - this->rtc_ = global_preferences.make_preference(233825507UL, false); + this->rtc_ = global_preferences->make_preference(233825507UL, false); this->safe_mode_rtc_value_ = this->read_rtc_(); ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 04cea56ec2..f76295735e 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -1,12 +1,10 @@ #pragma once -#include - +#include "esphome/components/socket/socket.h" #include "esphome/core/component.h" #include "esphome/core/preferences.h" #include "esphome/core/helpers.h" -#include -#include +#include "esphome/core/defines.h" namespace esphome { namespace ota { @@ -32,6 +30,7 @@ enum OTAResponseTypes { OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135, OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136, OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, + OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, OTA_RESPONSE_ERROR_UNKNOWN = 255, }; @@ -40,14 +39,9 @@ enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR }; /// OTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. class OTAComponent : public Component { public: - /** Set a plaintext password that OTA will use for authentication. - * - * Warning: This password will be stored in plaintext in the ROM and can be read - * by intruders. - * - * @param password The plaintext password. - */ - void set_auth_password(const std::string &password); +#ifdef USE_OTA_PASSWORD + void set_auth_password(const std::string &password) { password_ = password; } +#endif // USE_OTA_PASSWORD /// Manually set the port OTA should listen on. void set_port(uint16_t port); @@ -76,14 +70,17 @@ class OTAComponent : public Component { uint32_t read_rtc_(); void handle_(); - size_t wait_receive_(uint8_t *buf, size_t bytes, bool check_disconnected = true); + bool readall_(uint8_t *buf, size_t len); + bool writeall_(const uint8_t *buf, size_t len); +#ifdef USE_OTA_PASSWORD std::string password_; +#endif // USE_OTA_PASSWORD uint16_t port_; - std::unique_ptr server_{nullptr}; - WiFiClient client_{}; + std::unique_ptr server_; + std::unique_ptr client_; bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled. uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled. diff --git a/esphome/components/pca9685/pca9685_output.h b/esphome/components/pca9685/pca9685_output.h index bd3f7f15f9..5dd52b5510 100644 --- a/esphome/components/pca9685/pca9685_output.h +++ b/esphome/components/pca9685/pca9685_output.h @@ -20,9 +20,10 @@ extern const uint8_t PCA9685_MODE_OUTNE_LOW; class PCA9685Output; -class PCA9685Channel : public output::FloatOutput, public Parented { +class PCA9685Channel : public output::FloatOutput { public: void set_channel(uint8_t channel) { channel_ = channel; } + void set_parent(PCA9685Output *parent) { parent_ = parent; } protected: friend class PCA9685Output; @@ -30,6 +31,7 @@ class PCA9685Channel : public output::FloatOutput, public Parentedwrite_gpio_(); } -void PCF8574Component::pin_mode(uint8_t pin, uint8_t mode) { - switch (mode) { - case PCF8574_INPUT: - // Clear mode mask bit - this->mode_mask_ &= ~(1 << pin); - // Write GPIO to enable input mode - this->write_gpio_(); - break; - case PCF8574_OUTPUT: - // Set mode mask bit - this->mode_mask_ |= 1 << pin; - break; - default: - break; +void PCF8574Component::pin_mode(uint8_t pin, gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + // Clear mode mask bit + this->mode_mask_ &= ~(1 << pin); + // Write GPIO to enable input mode + this->write_gpio_(); + } else if (flags == gpio::FLAG_OUTPUT) { + // Set mode mask bit + this->mode_mask_ |= 1 << pin; } } bool PCF8574Component::read_gpio_() { @@ -87,7 +82,7 @@ bool PCF8574Component::write_gpio_() { uint8_t data[2]; data[0] = value; data[1] = value >> 8; - if (!this->write_bytes_raw(data, this->pcf8575_ ? 2 : 1)) { + if (this->write(data, this->pcf8575_ ? 2 : 1) != i2c::ERROR_OK) { this->status_set_warning(); return false; } @@ -97,12 +92,15 @@ bool PCF8574Component::write_gpio_() { } float PCF8574Component::get_setup_priority() const { return setup_priority::IO; } -void PCF8574GPIOPin::setup() { this->pin_mode(this->mode_); } +void PCF8574GPIOPin::setup() { pin_mode(flags_); } +void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -void PCF8574GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } -PCF8574GPIOPin::PCF8574GPIOPin(PCF8574Component *parent, uint8_t pin, uint8_t mode, bool inverted) - : GPIOPin(pin, mode, inverted), parent_(parent) {} +std::string PCF8574GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via PCF8574", pin_); + return buffer; +} } // namespace pcf8574 } // namespace esphome diff --git a/esphome/components/pcf8574/pcf8574.h b/esphome/components/pcf8574/pcf8574.h index 925fa30899..c201e0615f 100644 --- a/esphome/components/pcf8574/pcf8574.h +++ b/esphome/components/pcf8574/pcf8574.h @@ -1,18 +1,12 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" namespace esphome { namespace pcf8574 { -/// Modes for PCF8574 pins -enum PCF8574GPIOMode : uint8_t { - PCF8574_INPUT = INPUT, - PCF8574_OUTPUT = OUTPUT, -}; - class PCF8574Component : public Component, public i2c::I2CDevice { public: PCF8574Component() = default; @@ -26,7 +20,7 @@ class PCF8574Component : public Component, public i2c::I2CDevice { /// Helper function to write the value of a pin. void digital_write(uint8_t pin, bool value); /// Helper function to set the pin mode of a pin. - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; @@ -49,15 +43,22 @@ class PCF8574Component : public Component, public i2c::I2CDevice { /// Helper class to expose a PCF8574 pin as an internal input GPIO pin. class PCF8574GPIOPin : public GPIOPin { public: - PCF8574GPIOPin(PCF8574Component *parent, uint8_t pin, uint8_t mode, bool inverted = false); - void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(PCF8574Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: PCF8574Component *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace pcf8574 diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 83e4b6be32..15c1c5f076 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -72,7 +72,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce return res; } - if (!isnan(this->setpoint_) && this->setpoint_ != setpoint) { + if (!std::isnan(this->setpoint_) && this->setpoint_ != setpoint) { ESP_LOGW(TAG, "Setpoint changed during autotune! The result will not be accurate!"); } this->setpoint_ = setpoint; diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 4c7d92e26d..f5c7792782 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -100,7 +100,7 @@ void PIDClimate::write_output_(float value) { } void PIDClimate::update_pid_() { float value; - if (isnan(this->current_temperature) || isnan(this->target_temperature)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) { // if any control parameters are nan, turn off all outputs value = 0.0; } else { diff --git a/esphome/components/pid/pid_controller.h b/esphome/components/pid/pid_controller.h index 4caad8dd8b..35e3eb9fc0 100644 --- a/esphome/components/pid/pid_controller.h +++ b/esphome/components/pid/pid_controller.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace pid { @@ -23,9 +23,9 @@ struct PIDController { // i(t) := K_i * \int_{0}^{t} e(t) dt accumulated_integral_ += error * dt * ki; // constrain accumulated integral value - if (!isnan(min_integral) && accumulated_integral_ < min_integral) + if (!std::isnan(min_integral) && accumulated_integral_ < min_integral) accumulated_integral_ = min_integral; - if (!isnan(max_integral) && accumulated_integral_ > max_integral) + if (!std::isnan(max_integral) && accumulated_integral_ > max_integral) accumulated_integral_ = max_integral; integral_term = accumulated_integral_; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 8603adfbf4..7dbbd798ad 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -840,7 +840,7 @@ void Pipsolar::send_next_poll_() { this->used_polling_commands_[this->last_polling_command_].length); } -void Pipsolar::queue_command_(const char *command, byte length) { +void Pipsolar::queue_command_(const char *command, uint8_t length) { uint8_t next_position = command_queue_position_; for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; diff --git a/esphome/components/pipsolar/pipsolar.h b/esphome/components/pipsolar/pipsolar.h index 508036c7de..fe2a80d1d5 100644 --- a/esphome/components/pipsolar/pipsolar.h +++ b/esphome/components/pipsolar/pipsolar.h @@ -197,7 +197,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent { uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data); uint8_t send_next_command_(); void send_next_poll_(); - void queue_command_(const char *command, byte length); + void queue_command_(const char *command, uint8_t length); std::string command_queue_[COMMAND_QUEUE_LENGTH]; uint8_t command_queue_position_ = 0; uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH]; diff --git a/esphome/components/pmsa003i/pmsa003i.cpp b/esphome/components/pmsa003i/pmsa003i.cpp index 1396c9f3d4..ca3d28367a 100644 --- a/esphome/components/pmsa003i/pmsa003i.cpp +++ b/esphome/components/pmsa003i/pmsa003i.cpp @@ -1,5 +1,6 @@ #include "pmsa003i.h" #include "esphome/core/log.h" +#include namespace esphome { namespace pmsa003i { diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 1c4160539a..ed2a2c1e35 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -2,6 +2,7 @@ #include #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Based on: // - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index 25f24758bf..ef7480ec25 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -1,5 +1,6 @@ #include "pn532_i2c.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Based on: // - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf @@ -11,7 +12,7 @@ namespace pn532_i2c { static const char *const TAG = "pn532_i2c"; -bool PN532I2C::write_data(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } +bool PN532I2C::write_data(const std::vector &data) { return this->write(data.data(), data.size()); } bool PN532I2C::read_data(std::vector &data, uint8_t len) { delay(1); diff --git a/esphome/components/power_supply/power_supply.h b/esphome/components/power_supply/power_supply.h index 12d2a9984f..66e4a7565a 100644 --- a/esphome/components/power_supply/power_supply.h +++ b/esphome/components/power_supply/power_supply.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace power_supply { diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 06a0e39e2c..fa7b4fe132 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "prometheus_handler.h" #include "esphome/core/application.h" @@ -55,7 +57,7 @@ void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj) { if (obj->is_internal()) return; - if (!isnan(obj->state)) { + if (!std::isnan(obj->state)) { // We have a valid value, output this value stream->print(F("esphome_sensor_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -249,7 +251,7 @@ void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj) { if (obj->is_internal()) return; - if (!isnan(obj->position)) { + if (!std::isnan(obj->position)) { // We have a valid value, output this value stream->print(F("esphome_cover_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -310,3 +312,5 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch } // namespace prometheus } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 6abd406556..5076883ba6 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/controller.h" #include "esphome/core/component.h" @@ -79,3 +81,5 @@ class PrometheusHandler : public AsyncWebHandler, public Component { } // namespace prometheus } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 602c0bdd67..f538a4c905 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -8,15 +8,15 @@ static const char *const TAG = "pulse_counter"; const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; -#ifdef ARDUINO_ARCH_ESP8266 -void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { +#ifdef USE_ESP8266 +void IRAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { const uint32_t now = micros(); const bool discard = now - arg->last_pulse < arg->filter_us; arg->last_pulse = now; if (discard) return; - PulseCounterCountMode mode = arg->isr_pin->digital_read() ? arg->rising_edge_mode : arg->falling_edge_mode; + PulseCounterCountMode mode = arg->isr_pin.digital_read() ? arg->rising_edge_mode : arg->falling_edge_mode; switch (mode) { case PULSE_COUNTER_DISABLE: break; @@ -28,11 +28,11 @@ void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) { break; } } -bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { +bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { this->pin = pin; this->pin->setup(); this->isr_pin = this->pin->to_isr(); - this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, CHANGE); + this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); return true; } pulse_counter_t PulseCounterStorage::read_raw_value() { @@ -43,8 +43,8 @@ pulse_counter_t PulseCounterStorage::read_raw_value() { } #endif -#ifdef ARDUINO_ARCH_ESP32 -bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) { +#ifdef USE_ESP32 +bool PulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { static pcnt_unit_t next_pcnt_unit = PCNT_UNIT_0; this->pin = pin; this->pin->setup(); diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 3203ab81fd..94e37bc232 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -1,10 +1,10 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif @@ -17,30 +17,30 @@ enum PulseCounterCountMode { PULSE_COUNTER_DECREMENT, }; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 using pulse_counter_t = int16_t; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 using pulse_counter_t = int32_t; #endif struct PulseCounterStorage { - bool pulse_counter_setup(GPIOPin *pin); + bool pulse_counter_setup(InternalGPIOPin *pin); pulse_counter_t read_raw_value(); static void gpio_intr(PulseCounterStorage *arg); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 volatile pulse_counter_t counter{0}; volatile uint32_t last_pulse{0}; #endif - GPIOPin *pin; -#ifdef ARDUINO_ARCH_ESP32 + InternalGPIOPin *pin; +#ifdef USE_ESP32 pcnt_unit_t pcnt_unit; #endif -#ifdef ARDUINO_ARCH_ESP8266 - ISRInternalGPIOPin *isr_pin; +#ifdef USE_ESP8266 + ISRInternalGPIOPin isr_pin; #endif PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT}; PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE}; @@ -50,7 +50,7 @@ struct PulseCounterStorage { class PulseCounterSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_rising_edge_mode(PulseCounterCountMode mode) { storage_.rising_edge_mode = mode; } void set_falling_edge_mode(PulseCounterCountMode mode) { storage_.falling_edge_mode = mode; } void set_filter_us(uint32_t filter) { storage_.filter_us = filter; } @@ -63,7 +63,7 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { void dump_config() override; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; PulseCounterStorage storage_; uint32_t current_total_ = 0; sensor::Sensor *total_sensor_; diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 1a35deba2f..fd1403b4fd 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -9,7 +9,7 @@ static const char *const TAG = "pulse_meter"; void PulseMeterSensor::setup() { this->pin_->setup(); this->isr_pin_ = pin_->to_isr(); - this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, CHANGE); + this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->last_detected_edge_us_ = 0; this->last_valid_edge_us_ = 0; @@ -56,14 +56,14 @@ void PulseMeterSensor::dump_config() { ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); } -void ICACHE_RAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { +void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { // This is an interrupt handler - we can't call any virtual method from this method // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); // We only look at rising edges - if (!sensor->isr_pin_->digital_read()) { + if (!sensor->isr_pin_.digital_read()) { return; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index d02cff6123..1cebc1748e 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/core/helpers.h" @@ -10,7 +10,7 @@ namespace pulse_meter { class PulseMeterSensor : public sensor::Sensor, public Component { public: - void set_pin(GPIOPin *pin) { this->pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } @@ -25,8 +25,8 @@ class PulseMeterSensor : public sensor::Sensor, public Component { protected: static void gpio_intr(PulseMeterSensor *sensor); - GPIOPin *pin_ = nullptr; - ISRInternalGPIOPin *isr_pin_; + InternalGPIOPin *pin_ = nullptr; + ISRInternalGPIOPin isr_pin_; uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; sensor::Sensor *total_sensor_ = nullptr; diff --git a/esphome/components/pulse_width/pulse_width.cpp b/esphome/components/pulse_width/pulse_width.cpp index fb998ef4e1..8d66861049 100644 --- a/esphome/components/pulse_width/pulse_width.cpp +++ b/esphome/components/pulse_width/pulse_width.cpp @@ -6,8 +6,8 @@ namespace pulse_width { static const char *const TAG = "pulse_width"; -void ICACHE_RAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { - const bool new_level = arg->pin_->digital_read(); +void IRAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { + const bool new_level = arg->pin_.digital_read(); const uint32_t now = micros(); if (new_level) { arg->last_rise_ = now; diff --git a/esphome/components/pulse_width/pulse_width.h b/esphome/components/pulse_width/pulse_width.h index 9d32ce99b1..822688ec88 100644 --- a/esphome/components/pulse_width/pulse_width.h +++ b/esphome/components/pulse_width/pulse_width.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -10,11 +10,11 @@ namespace pulse_width { /// Store data in a class that doesn't use multiple-inheritance (vtables in flash) class PulseWidthSensorStore { public: - void setup(GPIOPin *pin) { + void setup(InternalGPIOPin *pin) { pin->setup(); this->pin_ = pin->to_isr(); this->last_rise_ = micros(); - pin->attach_interrupt(&PulseWidthSensorStore::gpio_intr, this, CHANGE); + pin->attach_interrupt(&PulseWidthSensorStore::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); } static void gpio_intr(PulseWidthSensorStore *arg); uint32_t get_pulse_width_us() const { return this->last_width_; } @@ -22,14 +22,14 @@ class PulseWidthSensorStore { uint32_t get_last_rise() const { return last_rise_; } protected: - ISRInternalGPIOPin *pin_; + ISRInternalGPIOPin pin_; volatile uint32_t last_width_{0}; volatile uint32_t last_rise_{0}; }; class PulseWidthSensor : public sensor::Sensor, public PollingComponent { public: - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void setup() override { this->store_.setup(this->pin_); } void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -37,7 +37,7 @@ class PulseWidthSensor : public sensor::Sensor, public PollingComponent { protected: PulseWidthSensorStore store_; - GPIOPin *pin_; + InternalGPIOPin *pin_; }; } // namespace pulse_width diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index 6c91104036..b090647627 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -26,9 +26,7 @@ CONFIG_SCHEMA = ( .extend( { cv.GenerateID(): cv.declare_id(PulseWidthSensor), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) .extend(cv.polling_component_schema("60s")) diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 9edcd9166c..ff9723ab2f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -1,7 +1,7 @@ #include "pvvx_mithermometer.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace pvvx_mithermometer { diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 4132954983..bb67769d4f 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace pvvx_mithermometer { diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index 9e80cdbd88..f03b6af191 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -1,5 +1,7 @@ #include "qmc5883l.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include namespace esphome { namespace qmc5883l { @@ -115,9 +117,10 @@ void QMC5883LComponent::update() { } bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { - bool success = this->read_byte_16(a_register, data); + if (!this->read_byte_16(a_register, data)) + return false; *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; - return success; + return true; } } // namespace qmc5883l diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 6261f63132..385641fea0 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -43,14 +43,14 @@ void RC522::setup() { // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. if (reset_pin_ != nullptr) { - reset_pin_->pin_mode(INPUT); + reset_pin_->pin_mode(gpio::FLAG_INPUT); - if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. + if (!reset_pin_->digital_read()) { // The MFRC522 chip is in power down mode. ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); - reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. - reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. + reset_pin_->pin_mode(gpio::FLAG_OUTPUT); // Now set the resetPowerDownPin as digital output. + reset_pin_->digital_write(false); // Make sure we have a clean LOW state. delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl - reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. + reset_pin_->digital_write(true); // Exit power down mode. This triggers a hard reset. // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. // Let us be generous: 50ms. reset_timeout_ = millis(); diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index e4e3387ff6..d853d2f5ff 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp index 896e27214a..6a3d8d2486 100644 --- a/esphome/components/rc522_i2c/rc522_i2c.cpp +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -18,8 +18,9 @@ void RC522I2C::dump_config() { uint8_t RC522I2C::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. ) { uint8_t value; - read_byte(reg >> 1, &value); - ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + if (!read_byte(reg >> 1, &value)) + return 0; + ESP_LOGVV(TAG, "read_register_(%x) -> %u", reg, value); return value; } diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 537ae2283c..d2b848600d 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1092,15 +1092,13 @@ MIDEA_SCHEMA = cv.Schema( [cv.Any(cv.hex_uint8_t, cv.uint8_t)], cv.Length(min=5, max=5), ), - cv.GenerateID(CONF_CODE_STORAGE_ID): cv.declare_id(cg.uint8), } ) @register_binary_sensor("midea", MideaBinarySensor, MIDEA_SCHEMA) def midea_binary_sensor(var, config): - arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) - cg.add(var.set_code(arr_)) + cg.add(var.set_code(config[CONF_CODE])) @register_trigger("midea", MideaTrigger, MideaData) @@ -1119,5 +1117,4 @@ def midea_dumper(var, config): MIDEA_SCHEMA, ) async def midea_action(var, config, args): - arr_ = cg.progmem_array(config[CONF_CODE_STORAGE_ID], config[CONF_CODE]) - cg.add(var.set_code(arr_)) + cg.add(var.set_code(config[CONF_CODE])) diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 9b0d156617..12916bd44d 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -17,8 +17,6 @@ class MideaData { MideaData(const std::vector &data) { memcpy(this->data_, data.data(), std::min(data.size(), sizeof(this->data_))); } - // Make 40-bit copy from PROGMEM array - MideaData(const uint8_t *data) { memcpy_P(this->data_, data, OFFSET_CS); } // Default copy constructor MideaData(const MideaData &) = default; @@ -83,7 +81,7 @@ class MideaBinarySensor : public RemoteReceiverBinarySensorBase { auto data = MideaProtocol().decode(src); return data.has_value() && data.value() == this->data_; } - void set_code(const uint8_t *code) { this->data_ = code; } + void set_code(const std::vector &code) { this->data_ = code; } protected: MideaData data_; @@ -93,7 +91,8 @@ using MideaTrigger = RemoteReceiverTrigger; using MideaDumper = RemoteReceiverDumper; template class MideaAction : public RemoteTransmitterActionBase { - TEMPLATABLE_VALUE(const uint8_t *, code) + TEMPLATABLE_VALUE(std::vector, code) + void set_code(std::vector code) { code_ = code; } void encode(RemoteTransmitData *dst, Ts... x) override { MideaData data = this->code_.value(x...); data.finalize(); diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index d36c0d7ebe..a43f743ab6 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -6,7 +6,7 @@ namespace remote_base { static const char *const TAG = "remote_base"; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; this->channel_ = next_rmt_channel; diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 16c732df83..68cf67d175 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -3,11 +3,11 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif @@ -146,13 +146,13 @@ template class RemoteProtocol { class RemoteComponentBase { public: - explicit RemoteComponentBase(GPIOPin *pin) : pin_(pin){}; + explicit RemoteComponentBase(InternalGPIOPin *pin) : pin_(pin){}; protected: - GPIOPin *pin_; + InternalGPIOPin *pin_; }; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 class RemoteRMTChannel { public: explicit RemoteRMTChannel(uint8_t mem_block_num = 1); @@ -178,7 +178,7 @@ class RemoteRMTChannel { class RemoteTransmitterBase : public RemoteComponentBase { public: - RemoteTransmitterBase(GPIOPin *pin) : RemoteComponentBase(pin) {} + RemoteTransmitterBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} class TransmitCall { public: explicit TransmitCall(RemoteTransmitterBase *parent) : parent_(parent) {} @@ -221,7 +221,7 @@ class RemoteReceiverDumperBase { class RemoteReceiverBase : public RemoteComponentBase { public: - RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {} + RemoteReceiverBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); } void register_dumper(RemoteReceiverDumperBase *dumper) { if (dumper->is_secondary()) { diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 0f2605d865..4571f332b3 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -1,5 +1,6 @@ #include "samsung_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index 27d042ace0..bd1d2a8f5b 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -1,5 +1,6 @@ #include "toshiba_ac_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 7158368ed8..253204bd1a 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -25,9 +25,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.Schema( { cv.GenerateID(): cv.declare_id(RemoteReceiverComponent), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_DUMP, default=[]): remote_base.validate_dumpers, cv.Optional(CONF_TOLERANCE, default=25): cv.All( cv.percentage_int, cv.Range(min=0) diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 25262e3366..50153c105d 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -6,7 +6,7 @@ namespace esphome { namespace remote_receiver { -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); @@ -21,23 +21,23 @@ struct RemoteReceiverComponentStore { bool overflow{false}; uint32_t buffer_size{1000}; uint8_t filter_us{10}; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; }; #endif class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, public Component -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 , public remote_base::RemoteRMTChannel #endif { public: -#ifdef ARDUINO_ARCH_ESP32 - RemoteReceiverComponent(GPIOPin *pin, uint8_t mem_block_num = 1) +#ifdef USE_ESP32 + RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} #else - RemoteReceiverComponent(GPIOPin *pin) : RemoteReceiverBase(pin) {} + RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {} #endif void setup() override; void dump_config() override; @@ -49,13 +49,13 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } protected: -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void decode_rmt_(rmt_item32_t *item, size_t len); RingbufHandle_t ringbuf_; esp_err_t error_code_{ESP_OK}; #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 RemoteReceiverComponentStore store_; HighFrequencyLoopRequester high_freq_; #endif diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index b2ddc69b1c..bec2af6718 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -1,7 +1,7 @@ #include "remote_receiver.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include namespace esphome { diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index d509eea925..cf2c15402e 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -1,20 +1,20 @@ #include "remote_receiver.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 namespace esphome { namespace remote_receiver { static const char *const TAG = "remote_receiver.esp8266"; -void ICACHE_RAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { +void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { const uint32_t now = micros(); // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; - const bool level = arg->pin->digital_read(); + const bool level = arg->pin.digital_read(); if (level != next % 2) return; @@ -54,7 +54,7 @@ void RemoteReceiverComponent::setup() { } else { s.buffer_write_at = s.buffer_read_at = 0; } - this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 853b5b6289..d05942de3b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -8,13 +8,13 @@ namespace remote_transmitter { class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, public Component -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 , public remote_base::RemoteRMTChannel #endif { public: - explicit RemoteTransmitterComponent(GPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} + explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} void setup() override; @@ -26,7 +26,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period); void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); @@ -34,7 +34,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void space_(uint32_t usec); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 void configure_rmt(); uint32_t current_carrier_frequency_{UINT32_MAX}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 90166d2741..ff53d0be84 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace remote_transmitter { diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index f8735fe763..33c01985d7 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 namespace esphome { namespace remote_transmitter { diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 1380354a5f..4d3dfa5928 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -13,7 +13,7 @@ void ResistanceSensor::dump_config() { ESP_LOGCONFIG(TAG, " Reference Voltage: %.1fV", this->reference_voltage_); } void ResistanceSensor::process_(float value) { - if (isnan(value)) { + if (std::isnan(value)) { this->publish_state(NAN); return; } diff --git a/esphome/components/restart/restart_switch.cpp b/esphome/components/restart/restart_switch.cpp index ea46c5f910..3076fde99e 100644 --- a/esphome/components/restart/restart_switch.cpp +++ b/esphome/components/restart/restart_switch.cpp @@ -1,4 +1,5 @@ #include "restart_switch.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 8ef6f932c5..7c95fac98e 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -82,12 +82,12 @@ static const uint16_t DRAM_ATTR STATE_LOOKUP_TABLE[32] = { STATE_CW | STATE_S3 // 0x1F: stay here }; -void ICACHE_RAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore *arg) { +void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore *arg) { // Forget upper bits and add pin states uint8_t input_state = arg->state & STATE_LUT_MASK; - if (arg->pin_a->digital_read()) + if (arg->pin_a.digital_read()) input_state |= STATE_PIN_A_HIGH; - if (arg->pin_b->digital_read()) + if (arg->pin_b.digital_read()) input_state |= STATE_PIN_B_HIGH; int8_t rotation_dir = 0; @@ -134,8 +134,8 @@ void RotaryEncoderSensor::setup() { this->pin_i_->setup(); } - this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); - this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, CHANGE); + this->pin_a_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); + this->pin_b_->attach_interrupt(RotaryEncoderSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void RotaryEncoderSensor::dump_config() { LOG_SENSOR("", "Rotary Encoder", this); @@ -157,13 +157,14 @@ void RotaryEncoderSensor::dump_config() { void RotaryEncoderSensor::loop() { std::array rotation_events; bool rotation_events_overflow; - ets_intr_lock(); - rotation_events = this->store_.rotation_events; - rotation_events_overflow = this->store_.rotation_events_overflow; + { + InterruptLock lock; + rotation_events = this->store_.rotation_events; + rotation_events_overflow = this->store_.rotation_events_overflow; - this->store_.rotation_events.fill(0); - this->store_.rotation_events_overflow = false; - ets_intr_unlock(); + this->store_.rotation_events.fill(0); + this->store_.rotation_events_overflow = false; + } if (rotation_events_overflow) { ESP_LOGW(TAG, "Captured more rotation events than expected"); diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 000350d66c..4825e472a1 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -3,7 +3,7 @@ #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include "esphome/components/sensor/sensor.h" @@ -19,8 +19,8 @@ enum RotaryEncoderResolution { }; struct RotaryEncoderSensorStore { - ISRInternalGPIOPin *pin_a; - ISRInternalGPIOPin *pin_b; + ISRInternalGPIOPin pin_a; + ISRInternalGPIOPin pin_b; volatile int32_t counter{0}; RotaryEncoderResolution resolution{ROTARY_ENCODER_1_PULSE_PER_CYCLE}; @@ -37,8 +37,8 @@ struct RotaryEncoderSensorStore { class RotaryEncoderSensor : public sensor::Sensor, public Component { public: - void set_pin_a(GPIOPin *pin_a) { pin_a_ = pin_a; } - void set_pin_b(GPIOPin *pin_b) { pin_b_ = pin_b; } + void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; } + void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; } /** Set the resolution of the rotary encoder. * @@ -76,8 +76,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { } protected: - GPIOPin *pin_a_; - GPIOPin *pin_b_; + InternalGPIOPin *pin_a_; + InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index e82b9d5f13..ef1110c6d8 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -64,12 +64,8 @@ CONFIG_SCHEMA = cv.All( .extend( { cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), - cv.Required(CONF_PIN_A): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_PIN_B): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN_A): cv.All(pins.internal_gpio_input_pin_schema), + cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index e3e9f42305..bdd012cf5c 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -1,7 +1,7 @@ #include "ruuvi_ble.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvi_ble { diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.h b/esphome/components/ruuvi_ble/ruuvi_ble.h index 848004f3d7..add431ce42 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.h +++ b/esphome/components/ruuvi_ble/ruuvi_ble.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvi_ble { diff --git a/esphome/components/ruuvitag/ruuvitag.cpp b/esphome/components/ruuvitag/ruuvitag.cpp index f4e4a72270..9b462b4794 100644 --- a/esphome/components/ruuvitag/ruuvitag.cpp +++ b/esphome/components/ruuvitag/ruuvitag.cpp @@ -1,7 +1,7 @@ #include "ruuvitag.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvitag { diff --git a/esphome/components/ruuvitag/ruuvitag.h b/esphome/components/ruuvitag/ruuvitag.h index 863c5775c2..63029ebb4d 100644 --- a/esphome/components/ruuvitag/ruuvitag.h +++ b/esphome/components/ruuvitag/ruuvitag.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/ruuvi_ble/ruuvi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace ruuvitag { diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 8dfd992abe..30775fdea4 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -1,5 +1,10 @@ #include "scd30.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#ifdef USE_ESP8266 +#include +#endif namespace esphome { namespace scd30 { @@ -23,7 +28,7 @@ static const uint16_t SCD30_CMD_SOFT_RESET = 0xD304; void SCD30Component::setup() { ESP_LOGCONFIG(TAG, "Setting up scd30..."); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 Wire.setClockStretchLimit(150000); #endif @@ -51,7 +56,7 @@ void SCD30Component::setup() { return; } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 // According ESP32 clock stretching is typically 30ms and up to 150ms "due to // internal calibration processes". The I2C peripheral only supports 13ms (at // least when running at 80MHz). @@ -73,7 +78,7 @@ void SCD30Component::setup() { return; } } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 delay(30); #endif @@ -83,7 +88,7 @@ void SCD30Component::setup() { this->mark_failed(); return; } -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 delay(30); #endif @@ -193,7 +198,7 @@ bool SCD30Component::write_command_(uint16_t command, uint16_t data) { raw[2] = data >> 8; raw[3] = data & 0xFF; raw[4] = sht_crc_(raw[2], raw[3]); - return this->write_bytes_raw(raw, 5); + return this->write(raw, 5); } uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { @@ -223,7 +228,7 @@ bool SCD30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index 5e6c5887ac..ba7a028f8e 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -17,29 +17,29 @@ void SDP3XComponent::update() { this->read_pressure_(); } void SDP3XComponent::setup() { ESP_LOGD(TAG, "Setting up SDP3X..."); - if (!this->write_bytes_raw(SDP3X_STOP_MEAS, 2)) { + if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason } - if (!this->write_bytes_raw(SDP3X_SOFT_RESET, 2)) { + if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } delay_microseconds_accurate(20000); - if (!this->write_bytes_raw(SDP3X_READ_ID1, 2)) { + if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); this->mark_failed(); return; } - if (!this->write_bytes_raw(SDP3X_READ_ID2, 2)) { + if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); this->mark_failed(); return; } uint8_t data[18]; - if (!this->read_bytes_raw(data, 18)) { + if (this->read(data, 18) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID SDP3X failed!"); this->mark_failed(); return; @@ -59,7 +59,7 @@ void SDP3XComponent::setup() { pressure_scale_factor_ = 240.0f * 100.0f; } - if (!this->write_bytes_raw(SDP3X_START_DP_AVG, 2)) { + if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); this->mark_failed(); return; @@ -77,7 +77,7 @@ void SDP3XComponent::dump_config() { void SDP3XComponent::read_pressure_() { uint8_t data[9]; - if (!this->read_bytes_raw(data, 9)) { + if (this->read(data, 9) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Couldn't read SDP3X data!"); this->status_set_warning(); return; diff --git a/esphome/components/sensor/automation.h b/esphome/components/sensor/automation.h index c70fb93963..8cd0adbeb2 100644 --- a/esphome/components/sensor/automation.h +++ b/esphome/components/sensor/automation.h @@ -40,7 +40,7 @@ class ValueRangeTrigger : public Trigger, public Component { template void set_max(V max) { this->max_ = max; } void setup() override { - this->rtc_ = global_preferences.make_preference(this->parent_->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->parent_->get_object_id_hash()); bool initial_state; if (this->rtc_.load(&initial_state)) { this->previous_in_range_ = initial_state; @@ -52,18 +52,18 @@ class ValueRangeTrigger : public Trigger, public Component { protected: void on_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; float local_min = this->min_.value(state); float local_max = this->max_.value(state); bool in_range; - if (isnan(local_min) && isnan(local_max)) { + if (std::isnan(local_min) && std::isnan(local_max)) { in_range = this->previous_in_range_; - } else if (isnan(local_min)) { + } else if (std::isnan(local_min)) { in_range = state <= local_max; - } else if (isnan(local_max)) { + } else if (std::isnan(local_max)) { in_range = state >= local_min; } else { in_range = local_min <= state && state <= local_max; @@ -92,9 +92,9 @@ template class SensorInRangeCondition : public Condition void set_max(float max) { this->max_ = max; } bool check(Ts... x) override { const float state = this->parent_->state; - if (isnan(this->min_)) { + if (std::isnan(this->min_)) { return state <= this->max_; - } else if (isnan(this->max_)) { + } else if (std::isnan(this->max_)) { return state >= this->min_; } else { return this->min_ <= state && state <= this->max_; diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index f048189959..63801e7996 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -1,6 +1,7 @@ #include "filter.h" #include "sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace sensor { @@ -35,7 +36,7 @@ MedianFilter::MedianFilter(size_t window_size, size_t send_every, size_t send_fi void MedianFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MedianFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MedianFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -71,7 +72,7 @@ MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at void MinFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MinFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MinFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -100,7 +101,7 @@ MaxFilter::MaxFilter(size_t window_size, size_t send_every, size_t send_first_at void MaxFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void MaxFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional MaxFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { while (this->queue_.size() >= this->window_size_) { this->queue_.pop_front(); } @@ -130,7 +131,7 @@ SlidingWindowMovingAverageFilter::SlidingWindowMovingAverageFilter(size_t window void SlidingWindowMovingAverageFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; } void SlidingWindowMovingAverageFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; } optional SlidingWindowMovingAverageFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { if (this->queue_.size() == this->window_size_) { this->sum_ -= this->queue_[0]; this->queue_.pop_front(); @@ -165,7 +166,7 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { - if (!isnan(value)) { + if (!std::isnan(value)) { if (this->first_value_) this->accumulator_ = value; else @@ -211,8 +212,8 @@ optional MultiplyFilter::new_value(float value) { return value * this->mu FilterOutValueFilter::FilterOutValueFilter(float value_to_filter_out) : value_to_filter_out_(value_to_filter_out) {} optional FilterOutValueFilter::new_value(float value) { - if (isnan(this->value_to_filter_out_)) { - if (isnan(value)) + if (std::isnan(this->value_to_filter_out_)) { + if (std::isnan(value)) return {}; else return value; @@ -243,9 +244,9 @@ optional ThrottleFilter::new_value(float value) { // DeltaFilter DeltaFilter::DeltaFilter(float min_delta) : min_delta_(min_delta), last_value_(NAN) {} optional DeltaFilter::new_value(float value) { - if (isnan(value)) + if (std::isnan(value)) return {}; - if (isnan(this->last_value_)) { + if (std::isnan(this->last_value_)) { return this->last_value_ = value; } if (fabsf(value - this->last_value_) >= this->min_delta_) { diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 0b018ddb2e..2e1ba587a2 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -1,5 +1,6 @@ #include "servo.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace servo { diff --git a/esphome/components/servo/servo.h b/esphome/components/servo/servo.h index d95a524a8b..e2e3823158 100644 --- a/esphome/components/servo/servo.h +++ b/esphome/components/servo/servo.h @@ -24,7 +24,7 @@ class Servo : public Component { void setup() override { float v; if (this->restore_) { - this->rtc_ = global_preferences.make_preference(global_servo_id); + this->rtc_ = global_preferences->make_preference(global_servo_id); global_servo_id++; if (this->rtc_.load(&v)) { this->output_->set_level(v); diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 5a6f438429..1a64a12907 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,6 +1,7 @@ #include "sgp30.h" #include "esphome/core/log.h" #include "esphome/core/application.h" +#include namespace esphome { namespace sgp30 { @@ -84,7 +85,7 @@ void SGP30Component::setup() { // Hash with compilation time // This ensures the baseline storage is cleared after OTA uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->baselines_storage_)) { ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2, @@ -172,7 +173,7 @@ void SGP30Component::send_env_data_() { float humidity = NAN; if (this->humidity_sensor_ != nullptr) humidity = this->humidity_sensor_->state; - if (isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { + if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { ESP_LOGW(TAG, "Compensation not possible yet: bad humidity data."); return; } else { @@ -182,7 +183,7 @@ void SGP30Component::send_env_data_() { if (this->temperature_sensor_ != nullptr) { temperature = float(this->temperature_sensor_->state); } - if (isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { + if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { ESP_LOGW(TAG, "Compensation not possible yet: bad temperature value data."); return; } else { @@ -334,7 +335,7 @@ bool SGP30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index 1a1909eeb0..fddd0255b8 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -1,5 +1,6 @@ -#include "esphome/core/log.h" #include "sgp40.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" #include namespace esphome { @@ -55,7 +56,7 @@ void SGP40Component::setup() { // Hash with compilation time // This ensures the baseline storage is cleared after OTA uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->baselines_storage_)) { this->state0_ = this->baselines_storage_.state0; @@ -165,7 +166,7 @@ uint16_t SGP40Component::measure_raw_() { if (this->humidity_sensor_ != nullptr) { humidity = this->humidity_sensor_->state; } - if (isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { + if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { humidity = 50; } @@ -173,7 +174,7 @@ uint16_t SGP40Component::measure_raw_() { if (this->temperature_sensor_ != nullptr) { temperature = float(this->temperature_sensor_->state); } - if (isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { + if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { temperature = 25; } @@ -191,9 +192,9 @@ uint16_t SGP40Component::measure_raw_() { command[6] = tempticks & 0xFF; command[7] = generate_crc_(command + 5, 2); - if (!this->write_bytes_raw(command, 8)) { + if (this->write(command, 8) != i2c::ERROR_OK) { this->status_set_warning(); - ESP_LOGD(TAG, "write_bytes_raw error"); + ESP_LOGD(TAG, "write error"); return UINT16_MAX; } delay(250); // NOLINT @@ -302,7 +303,7 @@ bool SGP40Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index be5b93c124..56a43d5161 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -103,7 +103,7 @@ bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 6d7c917b57..248f32c4de 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -12,7 +12,7 @@ void SHT4XComponent::start_heater_() { uint8_t cmd[] = {MEASURECOMMANDS[this->heater_command_]}; ESP_LOGD(TAG, "Heater turning on"); - this->write_bytes_raw(cmd, 1); + this->write(cmd, 1); } void SHT4XComponent::setup() { @@ -53,7 +53,7 @@ void SHT4XComponent::update() { uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]}; // Send command - this->write_bytes_raw(cmd, 1); + this->write(cmd, 1); this->set_timeout(10, [this]() { const uint8_t num_bytes = 6; diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index db3a3bf803..f2fb6bd5c3 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -1,5 +1,6 @@ #include "shtcx.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace shtcx { @@ -132,7 +133,7 @@ bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/shutdown/shutdown_switch.cpp b/esphome/components/shutdown/shutdown_switch.cpp index 87b755cab6..0e5853cc46 100644 --- a/esphome/components/shutdown/shutdown_switch.cpp +++ b/esphome/components/shutdown/shutdown_switch.cpp @@ -2,6 +2,13 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#ifdef USE_ESP32 +#include +#endif +#ifdef USE_ESP8266 +#include +#endif + namespace esphome { namespace shutdown { @@ -17,10 +24,10 @@ void ShutdownSwitch::write_state(bool state) { delay(100); // NOLINT App.run_safe_shutdown_hooks(); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance) #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_deep_sleep_start(); #endif } diff --git a/esphome/components/slow_pwm/slow_pwm_output.h b/esphome/components/slow_pwm/slow_pwm_output.h index 4a2c1d0a14..f0524f36d8 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.h +++ b/esphome/components/slow_pwm/slow_pwm_output.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" namespace esphome { diff --git a/esphome/components/sm16716/sm16716.h b/esphome/components/sm16716/sm16716.h index 85f78c8cf5..73414c0003 100644 --- a/esphome/components/sm16716/sm16716.h +++ b/esphome/components/sm16716/sm16716.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace sm16716 { diff --git a/esphome/components/sm2135/sm2135.h b/esphome/components/sm2135/sm2135.h index e39730579f..0277e9ba1c 100644 --- a/esphome/components/sm2135/sm2135.h +++ b/esphome/components/sm2135/sm2135.h @@ -1,8 +1,9 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace sm2135 { diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 4437878970..0d1ff6ecba 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -3,10 +3,12 @@ import esphome.config_validation as cv from esphome import pins from esphome.const import ( CONF_ID, + CONF_MODE, CONF_NUMBER, CONF_INVERTED, CONF_DATA_PIN, CONF_CLOCK_PIN, + CONF_OUTPUT, ) DEPENDENCIES = [] @@ -48,19 +50,36 @@ async def to_code(config): cg.add(var.set_sr_count(config[CONF_SR_COUNT])) -SN74HC595_OUTPUT_PIN_SCHEMA = cv.Schema( +def _validate_output_mode(value): + if value is not True: + raise cv.Invalid("Only output mode is supported") + return value + + +SN74HC595_PIN_SCHEMA = cv.All( { + cv.GenerateID(): cv.declare_id(SN74HC595GPIOPin), cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_, + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_OUTPUT, default=True): cv.All( + cv.boolean, _validate_output_mode + ), + }, + ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -SN74HC595_INPUT_PIN_SCHEMA = cv.Schema({}) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_SN74HC595, (SN74HC595_OUTPUT_PIN_SCHEMA, SN74HC595_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, SN74HC595_PIN_SCHEMA) async def sn74hc595_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_SN74HC595]) - return SN74HC595GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_INVERTED]) + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + return var diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index 596ebf755d..5ebf50e5cb 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -10,17 +10,17 @@ void SN74HC595Component::setup() { ESP_LOGCONFIG(TAG, "Setting up SN74HC595..."); if (this->have_oe_pin_) { // disable output - this->oe_pin_->pin_mode(OUTPUT); + this->oe_pin_->setup(); this->oe_pin_->digital_write(true); } // initialize output pins - this->clock_pin_->pin_mode(OUTPUT); - this->data_pin_->pin_mode(OUTPUT); - this->latch_pin_->pin_mode(OUTPUT); - this->clock_pin_->digital_write(LOW); - this->data_pin_->digital_write(LOW); - this->latch_pin_->digital_write(LOW); + this->clock_pin_->setup(); + this->data_pin_->setup(); + this->latch_pin_->setup(); + this->clock_pin_->digital_write(false); + this->data_pin_->digital_write(false); + this->latch_pin_->digital_write(false); // send state to shift register this->write_gpio_(); @@ -62,16 +62,14 @@ bool SN74HC595Component::write_gpio_() { float SN74HC595Component::get_setup_priority() const { return setup_priority::IO; } -void SN74HC595GPIOPin::setup() {} - -bool SN74HC595GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; } - void SN74HC595GPIOPin::digital_write(bool value) { this->parent_->digital_write_(this->pin_, value != this->inverted_); } - -SN74HC595GPIOPin::SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted) - : GPIOPin(pin, OUTPUT, inverted), parent_(parent) {} +std::string SN74HC595GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via SN74HC595", pin_); + return buffer; +} } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h index d6f9a68bc8..784019c3a6 100644 --- a/esphome/components/sn74hc595/sn74hc595.h +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace sn74hc595 { @@ -41,14 +41,20 @@ class SN74HC595Component : public Component { /// Helper class to expose a SC74HC595 pin as an internal output GPIO pin. class SN74HC595GPIOPin : public GPIOPin { public: - SN74HC595GPIOPin(SN74HC595Component *parent, uint8_t pin, bool inverted = false); - - void setup() override; - bool digital_read() override; + void setup() override {} + void pin_mode(gpio::Flags flags) override {} + bool digital_read() override { return false; } void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(SN74HC595Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } protected: SN74HC595Component *parent_; + uint8_t pin_; + bool inverted_; }; } // namespace sn74hc595 diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 895c775b19..2b6cd10e80 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -1,10 +1,10 @@ #include "sntp_component.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include "lwip/apps/sntp.h" #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "sntp.h" #endif @@ -20,13 +20,13 @@ static const char *const TAG = "sntp"; void SNTPComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up SNTP..."); -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (sntp_enabled()) { sntp_stop(); } sntp_setoperatingmode(SNTP_OPMODE_POLL); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 sntp_stop(); #endif diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index aa1bcd3b3c..1db24973e7 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -6,8 +6,9 @@ #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include +#include #endif namespace esphome { @@ -81,7 +82,7 @@ class BSDSocketImpl : public Socket { int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } ssize_t readv(const struct iovec *iov, int iovcnt) override { -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 +#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 // esp-idf v3 doesn't have readv, emulate it ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { @@ -97,6 +98,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(USE_ESP32) + // ESP-IDF v4 only has symbol lwip_readv + return ::lwip_readv(fd_, iov, iovcnt); #else return ::readv(fd_, iov, iovcnt); #endif @@ -104,7 +108,7 @@ class BSDSocketImpl : public Socket { ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } ssize_t writev(const struct iovec *iov, int iovcnt) override { -#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR < 4 +#if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 4 // esp-idf v3 doesn't have writev, emulate it ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { @@ -121,6 +125,9 @@ class BSDSocketImpl : public Socket { break; } return ret; +#elif defined(USE_ESP32) + // ESP-IDF v4 only has symbol lwip_writev + return ::lwip_writev(fd_, iov, iovcnt); #else return ::writev(fd_, iov, iovcnt); #endif diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index fbe8f929a0..f9697fd421 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -86,7 +86,7 @@ struct iovec { size_t iov_len; }; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY #undef INADDR_ANY @@ -97,7 +97,7 @@ struct iovec { #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) -#else // !ARDUINO_ARCH_ESP8266 +#else // !USE_ESP8266 #define ESPHOME_INADDR_ANY INADDR_ANY #define ESPHOME_INADDR_NONE INADDR_NONE #endif @@ -114,7 +114,7 @@ struct iovec { #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ARDUINO // arduino-esp32 declares a global var called INADDR_NONE which is replaced // by the define #ifdef INADDR_NONE @@ -125,7 +125,7 @@ typedef uint32_t socklen_t; #define ESPHOME_INADDR_ANY ((uint32_t) 0x00000000UL) #define ESPHOME_INADDR_NONE ((uint32_t) 0xFFFFFFFFUL) -#else // !ARDUINO_ARCH_ESP32 +#else // !USE_ESP32 #define ESPHOME_INADDR_ANY INADDR_ANY #define ESPHOME_INADDR_NONE INADDR_NONE #endif diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 366f0972ef..7521d1339f 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -11,6 +11,7 @@ #include #include +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -492,7 +493,7 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto sock = std::unique_ptr(new LWIPRawImpl(newpcb)); + auto sock = make_unique(newpcb); sock->init(); accepted_sockets_.push(std::move(sock)); return ERR_OK; diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 3180447711..d883142c81 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -8,12 +8,13 @@ namespace spi { static const char *const TAG = "spi"; -void ICACHE_RAM_ATTR HOT SPIComponent::disable() { +void IRAM_ATTR HOT SPIComponent::disable() { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->endTransaction(); } +#endif // USE_SPI_ARDUINO_BACKEND if (this->active_cs_) { - ESP_LOGVV(TAG, "Disabling SPI Chip on pin %u...", this->active_cs_->get_pin()); this->active_cs_->digital_write(true); this->active_cs_ = nullptr; } @@ -23,19 +24,37 @@ void SPIComponent::setup() { this->clk_->setup(); this->clk_->digital_write(true); +#ifdef USE_SPI_ARDUINO_BACKEND bool use_hw_spi = true; - if (this->clk_->is_inverted()) - use_hw_spi = false; const bool has_miso = this->miso_ != nullptr; const bool has_mosi = this->mosi_ != nullptr; - if (has_miso && this->miso_->is_inverted()) + int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1; + + if (!this->clk_->is_internal()) use_hw_spi = false; - if (has_mosi && this->mosi_->is_inverted()) + if (has_miso && !miso_->is_internal()) use_hw_spi = false; - int8_t clk_pin = this->clk_->get_pin(); - int8_t miso_pin = has_miso ? this->miso_->get_pin() : -1; - int8_t mosi_pin = has_mosi ? this->mosi_->get_pin() : -1; -#ifdef ARDUINO_ARCH_ESP8266 + if (has_mosi && !mosi_->is_internal()) + use_hw_spi = false; + if (use_hw_spi) { + auto *clk_internal = (InternalGPIOPin *) clk_; + auto *miso_internal = (InternalGPIOPin *) miso_; + auto *mosi_internal = (InternalGPIOPin *) mosi_; + + if (clk_internal->is_inverted()) + use_hw_spi = false; + if (has_miso && miso_internal->is_inverted()) + use_hw_spi = false; + if (has_mosi && mosi_internal->is_inverted()) + use_hw_spi = false; + + if (use_hw_spi) { + clk_pin = clk_internal->get_pin(); + miso_pin = has_miso ? miso_internal->get_pin() : -1; + mosi_pin = has_mosi ? mosi_internal->get_pin() : -1; + } + } +#ifdef USE_ESP8266 if (clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) { // pass } else if (clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)) { @@ -50,8 +69,8 @@ void SPIComponent::setup() { this->hw_spi_->begin(); return; } -#endif -#ifdef ARDUINO_ARCH_ESP32 +#endif // USE_ESP8266 +#ifdef USE_ESP32 static uint8_t spi_bus_num = 0; if (spi_bus_num >= 2) { use_hw_spi = false; @@ -67,7 +86,8 @@ void SPIComponent::setup() { this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); return; } -#endif +#endif // USE_ESP32 +#endif // USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { this->miso_->setup(); @@ -82,32 +102,28 @@ void SPIComponent::dump_config() { LOG_PIN(" CLK Pin: ", this->clk_); LOG_PIN(" MISO Pin: ", this->miso_); LOG_PIN(" MOSI Pin: ", this->mosi_); +#ifdef USE_SPI_ARDUINO_BACKEND ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); +#endif // USE_SPI_ARDUINO_BACKEND } float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } -void SPIComponent::debug_tx(uint8_t value) { - ESP_LOGVV(TAG, " TX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); -} -void SPIComponent::debug_rx(uint8_t value) { - ESP_LOGVV(TAG, " RX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); -} -void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } - void SPIComponent::cycle_clock_(bool value) { - uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) - while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) + uint32_t start = arch_get_cpu_cycle_count(); + while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) ; this->clk_->digital_write(value); start += this->wait_cycle_; - while (start - ESP.getCycleCount() < this->wait_cycle_) // NOLINT(readability-static-accessed-through-instance) + while (start - arch_get_cpu_cycle_count() < this->wait_cycle_) ; } // NOLINTNEXTLINE +#ifndef CLANG_TIDY #pragma GCC optimize("unroll-loops") // NOLINTNEXTLINE #pragma GCC optimize("O2") +#endif // CLANG_TIDY template uint8_t HOT SPIComponent::transfer_(uint8_t data) { @@ -153,15 +169,6 @@ uint8_t HOT SPIComponent::transfer_(uint8_t data) { } } -#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - if (WRITE) { - SPIComponent::debug_tx(data); - } - if (READ) { - SPIComponent::debug_rx(out_data); - } -#endif - App.feed_wdt(); return out_data; diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index eb8f9ce7ce..601a5c5a7e 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -1,8 +1,16 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" +#include + +#ifdef USE_ARDUINO +#define USE_SPI_ARDUINO_BACKEND +#endif + +#ifdef USE_SPI_ARDUINO_BACKEND #include +#endif namespace esphome { namespace spi { @@ -72,18 +80,22 @@ class SPIComponent : public Component { void dump_config() override; template uint8_t read_byte() { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { return this->hw_spi_->transfer(0x00); } +#endif // USE_SPI_ARDUINO_BACKEND return this->transfer_(0x00); } template void read_array(uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->transfer(data, length); return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { data[i] = this->read_byte(); } @@ -91,19 +103,23 @@ class SPIComponent : public Component { template void write_byte(uint8_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->write(data); return; } +#endif // USE_SPI_ARDUINO_BACKEND this->transfer_(data); } template void write_byte16(const uint16_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { this->hw_spi_->write16(data); return; } +#endif // USE_SPI_ARDUINO_BACKEND this->write_byte(data >> 8); this->write_byte(data); @@ -111,12 +127,14 @@ class SPIComponent : public Component { template void write_array16(const uint16_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { for (size_t i = 0; i < length; i++) { this->hw_spi_->write16(data[i]); } return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { this->write_byte16(data[i]); } @@ -124,11 +142,13 @@ class SPIComponent : public Component { template void write_array(const uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { auto *data_c = const_cast(data); this->hw_spi_->writeBytes(data_c, length); return; } +#endif // USE_SPI_ARDUINO_BACKEND for (size_t i = 0; i < length; i++) { this->write_byte(data[i]); } @@ -136,6 +156,7 @@ class SPIComponent : public Component { template uint8_t transfer_byte(uint8_t data) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { if (this->hw_spi_ != nullptr) { return this->hw_spi_->transfer(data); @@ -143,12 +164,14 @@ class SPIComponent : public Component { return this->transfer_(data); } } +#endif // USE_SPI_ARDUINO_BACKEND this->write_byte(data); return 0; } template void transfer_array(uint8_t *data, size_t length) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { if (this->miso_ != nullptr) { this->hw_spi_->transfer(data, length); @@ -157,6 +180,7 @@ class SPIComponent : public Component { } return; } +#endif // USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { for (size_t i = 0; i < length; i++) { @@ -169,18 +193,19 @@ class SPIComponent : public Component { template void enable(GPIOPin *cs) { - if (cs != nullptr) { - SPIComponent::debug_enable(cs->get_pin()); - } - +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { uint8_t data_mode = (uint8_t(CLOCK_POLARITY) << 1) | uint8_t(CLOCK_PHASE); SPISettings settings(DATA_RATE, BIT_ORDER, data_mode); this->hw_spi_->beginTransaction(settings); } else { +#endif // USE_SPI_ARDUINO_BACKEND this->clk_->digital_write(CLOCK_POLARITY); - this->wait_cycle_ = uint32_t(F_CPU) / DATA_RATE / 2ULL; + uint32_t cpu_freq_hz = arch_get_cpu_freq_hz(); + this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL; +#ifdef USE_SPI_ARDUINO_BACKEND } +#endif // USE_SPI_ARDUINO_BACKEND if (cs != nullptr) { this->active_cs_ = cs; @@ -195,10 +220,6 @@ class SPIComponent : public Component { protected: inline void cycle_clock_(bool value); - static void debug_enable(uint8_t pin); - static void debug_tx(uint8_t value); - static void debug_rx(uint8_t value); - template uint8_t transfer_(uint8_t data); @@ -206,7 +227,9 @@ class SPIComponent : public Component { GPIOPin *miso_{nullptr}; GPIOPin *mosi_{nullptr}; GPIOPin *active_cs_{nullptr}; +#ifdef USE_SPI_ARDUINO_BACKEND SPIClass *hw_spi_{nullptr}; +#endif // USE_SPI_ARDUINO_BACKEND uint32_t wait_cycle_; }; diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 3a31f4d607..472b7606ed 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -244,7 +244,7 @@ bool SPS30Component::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 0fe09709e7..2c54af7a67 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index fce9796008..45fda4870e 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -10,8 +10,8 @@ void I2CSSD1306::setup() { ESP_LOGCONFIG(TAG, "Setting up I2C SSD1306..."); this->init_reset_(); - this->raw_begin_transmission(); - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; diff --git a/esphome/components/ssd1322_base/ssd1322_base.h b/esphome/components/ssd1322_base/ssd1322_base.h index 125e374246..6a790c0199 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.h +++ b/esphome/components/ssd1322_base/ssd1322_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1325_base/ssd1325_base.h b/esphome/components/ssd1325_base/ssd1325_base.h index a06ba69a59..cca9412c43 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.h +++ b/esphome/components/ssd1325_base/ssd1325_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1327_base/ssd1327_base.h b/esphome/components/ssd1327_base/ssd1327_base.h index 03f360b258..35b021c71b 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.h +++ b/esphome/components/ssd1327_base/ssd1327_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp index 2967d2f9c4..e9e047bfb6 100644 --- a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp +++ b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp @@ -10,8 +10,8 @@ void I2CSSD1327::setup() { ESP_LOGCONFIG(TAG, "Setting up I2C SSD1327..."); this->init_reset_(); - this->raw_begin_transmission(); - if (!this->raw_end_transmission()) { + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; diff --git a/esphome/components/ssd1331_base/ssd1331_base.h b/esphome/components/ssd1331_base/ssd1331_base.h index 8d2bca5de0..b889a47fbe 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.h +++ b/esphome/components/ssd1331_base/ssd1331_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/ssd1351_base/ssd1351_base.h b/esphome/components/ssd1351_base/ssd1351_base.h index 2730f798b5..422e601f8b 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.h +++ b/esphome/components/ssd1351_base/ssd1351_base.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/display/display_buffer.h" namespace esphome { diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 1f14136637..8490aa1fe4 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -1,6 +1,7 @@ #include "st7735.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace st7735 { @@ -353,17 +354,17 @@ void ST7735::display_init_(const uint8_t *addr) { uint8_t num_commands, cmd, num_args; uint16_t ms; - num_commands = pgm_read_byte(addr++); // Number of commands to follow - while (num_commands--) { // For each command... - cmd = pgm_read_byte(addr++); // Read command - num_args = pgm_read_byte(addr++); // Number of args to follow - ms = num_args & ST_CMD_DELAY; // If hibit set, delay follows args - num_args &= ~ST_CMD_DELAY; // Mask out delay bit + num_commands = progmem_read_byte(addr++); // Number of commands to follow + while (num_commands--) { // For each command... + cmd = progmem_read_byte(addr++); // Read command + num_args = progmem_read_byte(addr++); // Number of args to follow + ms = num_args & ST_CMD_DELAY; // If hibit set, delay follows args + num_args &= ~ST_CMD_DELAY; // Mask out delay bit this->sendcommand_(cmd, addr, num_args); addr += num_args; if (ms) { - ms = pgm_read_byte(addr++); // Read post-command delay time (ms) + ms = progmem_read_byte(addr++); // Read post-command delay time (ms) if (ms == 255) ms = 500; // If 255, delay for 500 ms delay(ms); @@ -410,7 +411,7 @@ void HOT ST7735::senddata_(const uint8_t *data_bytes, uint8_t num_data_bytes) { this->cs_->digital_write(false); this->enable(); for (uint8_t i = 0; i < num_data_bytes; i++) { - this->write_byte(pgm_read_byte(data_bytes++)); // write byte - SPI library + this->write_byte(progmem_read_byte(data_bytes++)); // write byte - SPI library } this->cs_->digital_write(true); this->disable(); diff --git a/esphome/components/status/status_binary_sensor.cpp b/esphome/components/status/status_binary_sensor.cpp index 152e3aff9d..1795a9c41b 100644 --- a/esphome/components/status/status_binary_sensor.cpp +++ b/esphome/components/status/status_binary_sensor.cpp @@ -1,6 +1,6 @@ #include "status_binary_sensor.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/defines.h" #ifdef USE_MQTT @@ -16,7 +16,7 @@ namespace status { static const char *const TAG = "status"; void StatusBinarySensor::loop() { - bool status = network_is_connected(); + bool status = network::is_connected(); #ifdef USE_MQTT if (mqtt::global_mqtt_client != nullptr) { status = status && mqtt::global_mqtt_client->is_connected(); diff --git a/esphome/components/status_led/light/status_led_light.h b/esphome/components/status_led/light/status_led_light.h index 8a7f4b4da8..e90d381e3c 100644 --- a/esphome/components/status_led/light/status_led_light.h +++ b/esphome/components/status_led/light/status_led_light.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/light/light_output.h" namespace esphome { diff --git a/esphome/components/status_led/status_led.h b/esphome/components/status_led/status_led.h index 79f9f524a3..490557f3e7 100644 --- a/esphome/components/status_led/status_led.h +++ b/esphome/components/status_led/status_led.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { namespace status_led { diff --git a/esphome/components/stepper/stepper.cpp b/esphome/components/stepper/stepper.cpp index d7f6cc6dda..7926024204 100644 --- a/esphome/components/stepper/stepper.cpp +++ b/esphome/components/stepper/stepper.cpp @@ -1,5 +1,6 @@ #include "stepper.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace stepper { diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index 77383c6daa..b1ecbc98f8 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -99,7 +99,7 @@ bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) { const uint8_t num_bytes = len * 3; std::vector buf(num_bytes); - if (!this->parent_->raw_receive(this->address_, buf.data(), num_bytes)) { + if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) { return false; } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 6a8364a5f0..0a2e6bcf97 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -80,7 +80,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedparent_->elevation(); - if (isnan(current)) + if (std::isnan(current)) return; bool crossed; @@ -90,7 +90,7 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parentedlast_elevation_ >= this->elevation_ && this->elevation_ > current; } - if (crossed && !isnan(this->last_elevation_)) { + if (crossed && !std::isnan(this->last_elevation_)) { this->trigger(); } this->last_elevation_ = current; diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index c96f9a40d0..0e12f5af0f 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -30,7 +30,7 @@ void Switch::toggle() { this->write_state(this->inverted_ == this->state); } optional Switch::get_initial_state() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); bool initial_state; if (!this->rtc_.load(&initial_state)) return {}; diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index 8e1239924a..f1b7d5f424 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -2,7 +2,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import i2c -from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_OUTPUT, + CONF_PULLUP, +) CONF_KEYPAD = "keypad" CONF_KEY_ROWS = "key_rows" @@ -10,17 +18,12 @@ CONF_KEY_COLUMNS = "key_columns" CONF_SLEEP_TIME = "sleep_time" CONF_SCAN_TIME = "scan_time" CONF_DEBOUNCE_TIME = "debounce_time" +CONF_SX1509_ID = "sx1509_id" DEPENDENCIES = ["i2c"] MULTI_CONF = True sx1509_ns = cg.esphome_ns.namespace("sx1509") -SX1509GPIOMode = sx1509_ns.enum("SX1509GPIOMode") -SX1509_GPIO_MODES = { - "INPUT": SX1509GPIOMode.SX1509_INPUT, - "INPUT_PULLUP": SX1509GPIOMode.SX1509_INPUT_PULLUP, - "OUTPUT": SX1509GPIOMode.SX1509_OUTPUT, -} SX1509Component = sx1509_ns.class_("SX1509Component", cg.Component, i2c.I2CDevice) SX1509GPIOPin = sx1509_ns.class_("SX1509GPIOPin", cg.GPIOPin) @@ -64,34 +67,43 @@ async def to_code(config): cg.add(var.set_debounce_time(keypad[CONF_DEBOUNCE_TIME])) -CONF_SX1509 = "sx1509" -CONF_SX1509_ID = "sx1509_id" +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value -SX1509_OUTPUT_PIN_SCHEMA = cv.Schema( + +CONF_SX1509 = "sx1509" +SX1509_PIN_SCHEMA = cv.All( { - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum( - SX1509_GPIO_MODES, upper=True + cv.GenerateID(): cv.declare_id(SX1509Component), + cv.Required(CONF_SX1509): cv.use_id(SX1509GPIOPin), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, ), cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) -SX1509_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_, - cv.Optional(CONF_MODE, default="INPUT"): cv.enum(SX1509_GPIO_MODES, upper=True), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) -@pins.PIN_SCHEMA_REGISTRY.register( - CONF_SX1509, (SX1509_OUTPUT_PIN_SCHEMA, SX1509_INPUT_PIN_SCHEMA) -) +@pins.PIN_SCHEMA_REGISTRY.register(CONF_SX1509, SX1509_PIN_SCHEMA) async def sx1509_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_SX1509]) - return SX1509GPIOPin.new( - parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED] - ) + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp index c68f8f9ded..e9c401eeed 100644 --- a/esphome/components/sx1509/output/sx1509_float_output.cpp +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -16,7 +16,8 @@ void SX1509FloatOutputChannel::write_state(float state) { void SX1509FloatOutputChannel::setup() { ESP_LOGD(TAG, "setup pin %d", this->pin_); - this->parent_->pin_mode(this->pin_, SX1509_ANALOG_OUTPUT); + this->parent_->pin_mode(this->pin_, gpio::FLAG_OUTPUT); + this->parent_->setup_led_driver(this->pin_); this->turn_off(); } diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 14d9ad7a61..9095dfeffa 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -18,13 +18,15 @@ void SX1509Component::setup() { this->write_byte(REG_RESET, 0x34); uint16_t data; - this->read_byte_16(REG_INTERRUPT_MASK_A, &data); - if (data == 0xFF00) { - clock_(INTERNAL_CLOCK_2MHZ); - } else { + if (!this->read_byte_16(REG_INTERRUPT_MASK_A, &data)) { this->mark_failed(); return; } + if (data != 0xFF00) { + this->mark_failed(); + return; + } + clock_(INTERNAL_CLOCK_2MHZ); delayMicroseconds(500); if (this->has_keypad_) this->setup_keypad_(); @@ -49,7 +51,8 @@ void SX1509Component::loop() { bool SX1509Component::digital_read(uint8_t pin) { if (this->ddr_mask_ & (1 << pin)) { uint16_t temp_reg_data; - this->read_byte_16(REG_DATA_B, &temp_reg_data); + if (!this->read_byte_16(REG_DATA_B, &temp_reg_data)) + return false; if (temp_reg_data & (1 << pin)) return true; } @@ -68,9 +71,9 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { this->write_byte_16(REG_DATA_B, temp_reg_data); } else { // Otherwise the pin is an input, pull-up/down - uint16_t temp_pullup; + uint16_t temp_pullup = 0; this->read_byte_16(REG_PULL_UP_B, &temp_pullup); - uint16_t temp_pull_down; + uint16_t temp_pull_down = 0; this->read_byte_16(REG_PULL_DOWN_B, &temp_pull_down); if (bit_value) { @@ -89,25 +92,21 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { } } -void SX1509Component::pin_mode(uint8_t pin, uint8_t mode) { +void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) { this->read_byte_16(REG_DIR_B, &this->ddr_mask_); - if ((mode == SX1509_OUTPUT) || (mode == SX1509_ANALOG_OUTPUT)) + if (flags == gpio::FLAG_OUTPUT) this->ddr_mask_ &= ~(1 << pin); else this->ddr_mask_ |= (1 << pin); this->write_byte_16(REG_DIR_B, this->ddr_mask_); - if (mode == INPUT_PULLUP) - digital_write(pin, HIGH); - - if (mode == SX1509_ANALOG_OUTPUT) { - setup_led_driver_(pin); - } + if (flags & gpio::FLAG_PULLUP) + digital_write(pin, true); } -void SX1509Component::setup_led_driver_(uint8_t pin) { - uint16_t temp_word; - uint8_t temp_byte; +void SX1509Component::setup_led_driver(uint8_t pin) { + uint16_t temp_word = 0; + uint8_t temp_byte = 0; this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word); temp_word |= (1 << pin); @@ -140,18 +139,18 @@ void SX1509Component::setup_led_driver_(uint8_t pin) { this->write_byte_16(REG_DATA_B, temp_word); } -void SX1509Component::clock_(byte osc_source, byte osc_pin_function, byte osc_freq_out, byte osc_divider) { +void SX1509Component::clock_(uint8_t osc_source, uint8_t osc_pin_function, uint8_t osc_freq_out, uint8_t osc_divider) { osc_source = (osc_source & 0b11) << 5; // 2-bit value, bits 6:5 osc_pin_function = (osc_pin_function & 1) << 4; // 1-bit value bit 4 osc_freq_out = (osc_freq_out & 0b1111); // 4-bit value, bits 3:0 uint8_t reg_clock = osc_source | osc_pin_function | osc_freq_out; this->write_byte(REG_CLOCK, reg_clock); - osc_divider = constrain(osc_divider, 1, 7); + osc_divider = clamp(osc_divider, 1, 7u); this->clk_x_ = 2000000; osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4 - uint8_t reg_misc; + uint8_t reg_misc = 0; this->read_byte(REG_MISC, ®_misc); reg_misc &= ~(0b111 << 4); reg_misc |= osc_divider; @@ -159,7 +158,7 @@ void SX1509Component::clock_(byte osc_source, byte osc_pin_function, byte osc_fr } void SX1509Component::setup_keypad_() { - uint8_t temp_byte; + uint8_t temp_byte = 0; // setup row/col pins for INPUT OUTPUT this->read_byte_16(REG_DIR_B, &this->ddr_mask_); @@ -199,14 +198,14 @@ void SX1509Component::setup_keypad_() { } uint16_t SX1509Component::read_key_data() { - uint16_t key_data; + uint16_t key_data = 0; this->read_byte_16(REG_KEY_DATA_1, &key_data); return (0xFFFF ^ key_data); } void SX1509Component::set_debounce_config_(uint8_t config_value) { // First make sure clock is configured - uint8_t temp_byte; + uint8_t temp_byte = 0; this->read_byte(REG_MISC, &temp_byte); temp_byte |= (1 << 4); // Just default to no divider if not set this->write_byte(REG_MISC, temp_byte); @@ -227,13 +226,13 @@ void SX1509Component::set_debounce_time_(uint8_t time) { break; } } - config_value = constrain(config_value, 0, 7); + config_value = clamp(config_value, 0, 7); set_debounce_config_(config_value); } void SX1509Component::set_debounce_enable_(uint8_t pin) { - uint16_t debounce_enable; + uint16_t debounce_enable = 0; this->read_byte_16(REG_DEBOUNCE_ENABLE_B, &debounce_enable); debounce_enable |= (1 << pin); this->write_byte_16(REG_DEBOUNCE_ENABLE_B, debounce_enable); diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 1390059675..5f0697b534 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -2,7 +2,7 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "sx1509_gpio_pin.h" #include "sx1509_registers.h" @@ -15,16 +15,6 @@ const uint8_t EXTERNAL_CLOCK = 1; const uint8_t SOFTWARE_RESET = 0; const uint8_t HARDWARE_RESET = 1; -const uint8_t ANALOG_OUTPUT = 0x03; // To set a pin mode for PWM output - -// PinModes for SX1509 pins -enum SX1509GPIOMode : uint8_t { - SX1509_INPUT = INPUT, // 0x00 - SX1509_INPUT_PULLUP = INPUT_PULLUP, // 0x02 - SX1509_ANALOG_OUTPUT = ANALOG_OUTPUT, // 0x03 - SX1509_OUTPUT = OUTPUT, // 0x01 -}; - const uint8_t REG_I_ON[16] = {REG_I_ON_0, REG_I_ON_1, REG_I_ON_2, REG_I_ON_3, REG_I_ON_4, REG_I_ON_5, REG_I_ON_6, REG_I_ON_7, REG_I_ON_8, REG_I_ON_9, REG_I_ON_10, REG_I_ON_11, REG_I_ON_12, REG_I_ON_13, REG_I_ON_14, REG_I_ON_15}; @@ -47,9 +37,9 @@ class SX1509Component : public Component, public i2c::I2CDevice { bool digital_read(uint8_t pin); uint16_t read_key_data(); void set_pin_value(uint8_t pin, uint8_t i_on) { this->write_byte(REG_I_ON[pin], i_on); }; - void pin_mode(uint8_t pin, uint8_t mode); + void pin_mode(uint8_t pin, gpio::Flags flags); void digital_write(uint8_t pin, bool bit_value); - u_long get_clock() { return this->clk_x_; }; + uint32_t get_clock() { return this->clk_x_; }; void set_rows_cols(uint8_t rows, uint8_t cols) { this->rows_ = rows; this->cols_ = cols; @@ -60,10 +50,11 @@ class SX1509Component : public Component, public i2c::I2CDevice { void set_debounce_time(uint8_t debounce_time = 1) { this->debounce_time_ = debounce_time; }; void register_keypad_binary_sensor(SX1509Processor *binary_sensor) { this->keypad_binary_sensors_.push_back(binary_sensor); - }; + } + void setup_led_driver(uint8_t pin); protected: - u_long clk_x_ = 2000000; + uint32_t clk_x_ = 2000000; uint8_t frequency_ = 0; uint16_t ddr_mask_ = 0x00; uint16_t input_mask_ = 0x00; @@ -82,7 +73,6 @@ class SX1509Component : public Component, public i2c::I2CDevice { void set_debounce_pin_(uint8_t pin); void set_debounce_enable_(uint8_t pin); void set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols); - void setup_led_driver_(uint8_t pin); void clock_(uint8_t osc_source = 2, uint8_t osc_pin_function = 1, uint8_t osc_freq_out = 0, uint8_t osc_divider = 0); }; diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index ac55ac1ca5..2c6e0b0c32 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -7,14 +7,15 @@ namespace sx1509 { static const char *const TAG = "sx1509_gpio_pin"; -void SX1509GPIOPin::setup() { - ESP_LOGD(TAG, "setup pin %d", this->pin_); - this->parent_->pin_mode(this->pin_, this->mode_); -} - -void SX1509GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +void SX1509GPIOPin::setup() { pin_mode(flags_); } +void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string SX1509GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); + return buffer; +} } // namespace sx1509 } // namespace esphome diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index 39f841a2a4..4d8aa5ec83 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -9,15 +9,22 @@ class SX1509Component; class SX1509GPIOPin : public GPIOPin { public: - SX1509GPIOPin(SX1509Component *parent, uint8_t pin, uint8_t mode, bool inverted = false) - : GPIOPin(pin, mode, inverted), parent_(parent){}; void setup() override; - void pin_mode(uint8_t mode) override; + void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(SX1509Component *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } protected: SX1509Component *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; }; } // namespace sx1509 diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py index 62cbace56a..0f222b8fc7 100644 --- a/esphome/components/tca9548a/__init__.py +++ b/esphome/components/tca9548a/__init__.py @@ -1,30 +1,43 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c -from esphome.const import CONF_ID, CONF_SCAN +from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID, CONF_SCAN CODEOWNERS = ["@andreashergert1984"] DEPENDENCIES = ["i2c"] tca9548a_ns = cg.esphome_ns.namespace("tca9548a") -TCA9548AComponent = tca9548a_ns.class_( - "TCA9548AComponent", cg.PollingComponent, i2c.I2CMultiplexer -) +TCA9548AComponent = tca9548a_ns.class_("TCA9548AComponent", cg.Component, i2c.I2CDevice) +TCA9548AChannel = tca9548a_ns.class_("TCA9548AChannel", i2c.I2CBus) MULTI_CONF = True -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(TCA9548AComponent), - cv.Optional(CONF_SCAN, default=True): cv.boolean, - } -).extend(i2c.i2c_device_schema(0x70)) +CONF_BUS_ID = "bus_id" +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TCA9548AComponent), + cv.Optional(CONF_SCAN): cv.invalid("This option has been removed"), + cv.Optional(CONF_CHANNELS, default=[]): cv.ensure_list( + { + cv.Required(CONF_BUS_ID): cv.declare_id(TCA9548AChannel), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } + ), + } + ) + .extend(i2c.i2c_device_schema(0x70)) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - cg.add_define("USE_I2C_MULTIPLEXER") await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - cg.add(var.set_scan(config[CONF_SCAN])) + + for conf in config[CONF_CHANNELS]: + chan = cg.new_Pvariable(conf[CONF_BUS_ID]) + cg.add(chan.set_parent(var)) + cg.add(chan.set_channel(conf[CONF_CHANNEL])) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 472b8b6673..5117ad8969 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -6,35 +6,46 @@ namespace tca9548a { static const char *const TAG = "tca9548a"; +i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) { + auto err = parent_->switch_to_channel(channel_); + if (err != i2c::ERROR_OK) + return err; + return parent_->bus_->readv(address, buffers, cnt); +} +i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) { + auto err = parent_->switch_to_channel(channel_); + if (err != i2c::ERROR_OK) + return err; + return parent_->bus_->writev(address, buffers, cnt); +} + void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (!this->read_byte(0x00, &status)) { + if (!this->read_register(0x00, &status, 1)) { ESP_LOGI(TAG, "TCA9548A failed"); + this->mark_failed(); return; } - // out of range to make sure on first set_channel a new one will be set - this->current_channelno_ = 8; - ESP_LOGCONFIG(TAG, "Channels currently open: %d", status); + ESP_LOGD(TAG, "Channels currently open: %d", status); } void TCA9548AComponent::dump_config() { ESP_LOGCONFIG(TAG, "TCA9548A:"); LOG_I2C_DEVICE(this); - if (this->scan_) { - for (uint8_t i = 0; i < 8; i++) { - ESP_LOGCONFIG(TAG, "Activating channel: %d", i); - this->set_channel(i); - this->parent_->dump_config(); - } - } } -void TCA9548AComponent::set_channel(uint8_t channelno) { - if (this->current_channelno_ != channelno) { - this->current_channelno_ = channelno; - uint8_t channelbyte = 1 << channelno; - this->write_byte(0x70, channelbyte); +i2c::ErrorCode TCA9548AComponent::switch_to_channel(uint8_t channel) { + if (this->is_failed()) + return i2c::ERROR_NOT_INITIALIZED; + if (current_channel_ == channel) + return i2c::ERROR_OK; + + uint8_t channel_val = 1 << channel; + auto err = this->write_register(0x70, &channel_val, 1); + if (err == i2c::ERROR_OK) { + current_channel_ = channel; } + return err; } } // namespace tca9548a diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h index 50b1eb8b56..314346d317 100644 --- a/esphome/components/tca9548a/tca9548a.h +++ b/esphome/components/tca9548a/tca9548a.h @@ -6,17 +6,31 @@ namespace esphome { namespace tca9548a { -class TCA9548AComponent : public Component, public i2c::I2CMultiplexer { +class TCA9548AComponent; +class TCA9548AChannel : public i2c::I2CBus { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + void set_parent(TCA9548AComponent *parent) { parent_ = parent; } + + i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override; + i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) override; + + protected: + uint8_t channel_; + TCA9548AComponent *parent_; +}; + +class TCA9548AComponent : public Component, public i2c::I2CDevice { public: - void set_scan(bool scan) { scan_ = scan; } void setup() override; void dump_config() override; void update(); - void set_channel(uint8_t channelno) override; + + i2c::ErrorCode switch_to_channel(uint8_t channel); protected: - bool scan_; - uint8_t current_channelno_; + friend class TCA9548AChannel; + uint8_t current_channel_ = 255; }; } // namespace tca9548a } // namespace esphome diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 52548262c1..564d3dcda7 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -1,5 +1,6 @@ #include "tcs34725.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tcs34725 { diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index eb9b17b976..a5b015c44d 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -14,9 +14,9 @@ void TemplateNumber::setup() { if (!this->restore_value_) { value = this->initial_value_; } else { - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) + if (!std::isnan(this->initial_value_)) value = this->initial_value_; else value = this->traits.get_min_value(); diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 8695880856..219c341ec9 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -17,7 +17,7 @@ void TemplateSelect::setup() { ESP_LOGD(TAG, "State from initial: %s", value.c_str()); } else { size_t index; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&index)) { value = this->initial_option_; ESP_LOGD(TAG, "State from initial (could not load): %s", value.c_str()); diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 63cbd70db0..b28eb3fed2 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -1,5 +1,6 @@ #include "template_sensor.h" #include "esphome/core/log.h" +#include namespace esphome { namespace template_ { @@ -12,7 +13,7 @@ void TemplateSensor::update() { if (val.has_value()) { this->publish_state(*val); } - } else if (!isnan(this->get_raw_state())) { + } else if (!std::isnan(this->get_raw_state())) { this->publish_state(this->get_raw_state()); } } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a75713cbb9..6193185321 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -88,17 +88,17 @@ climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && - (isnan(this->cooling_deadband_) || isnan(this->cooling_overrun_))) + (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_))) return false; - if (this->supports_heat_ && (isnan(this->heating_deadband_) || isnan(this->heating_overrun_))) + if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_))) return false; return true; } void ThermostatClimate::validate_target_temperature() { - if (isnan(this->target_temperature)) { + if (std::isnan(this->target_temperature)) { this->target_temperature = ((this->get_traits().get_visual_max_temperature() - this->get_traits().get_visual_min_temperature()) / 2) + this->get_traits().get_visual_min_temperature(); @@ -121,7 +121,7 @@ void ThermostatClimate::validate_target_temperatures() { } void ThermostatClimate::validate_target_temperature_low() { - if (isnan(this->target_temperature_low)) { + if (std::isnan(this->target_temperature_low)) { this->target_temperature_low = this->get_traits().get_visual_min_temperature(); } else { // target_temperature_low must not be lower than the visual minimum @@ -139,7 +139,7 @@ void ThermostatClimate::validate_target_temperature_low() { } void ThermostatClimate::validate_target_temperature_high() { - if (isnan(this->target_temperature_high)) { + if (std::isnan(this->target_temperature_high)) { this->target_temperature_high = this->get_traits().get_visual_max_temperature(); } else { // target_temperature_high must not be lower than the visual maximum @@ -245,7 +245,7 @@ climate::ClimateTraits ThermostatClimate::traits() { climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_timers) { auto target_action = climate::CLIMATE_ACTION_IDLE; // if any hysteresis values or current_temperature is not valid, we go to OFF; - if (isnan(this->current_temperature) || !this->hysteresis_valid()) { + if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; } // do not change the action if an "ON" timer is running @@ -307,7 +307,7 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { auto target_action = climate::CLIMATE_ACTION_IDLE; // if any hysteresis values or current_temperature is not valid, we go to OFF; - if (isnan(this->current_temperature) || !this->hysteresis_valid()) { + if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) { return climate::CLIMATE_ACTION_OFF; } diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index f133ab021c..7e16d7141f 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -1,5 +1,6 @@ #include "automation.h" #include "esphome/core/log.h" +#include namespace esphome { namespace time { diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 27a2d84da6..064e6f899c 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -1,7 +1,7 @@ #include "real_time_clock.h" #include "esphome/core/log.h" #include "lwip/opt.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include "sys/time.h" #endif #include diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 60a33aa82a..3fa07167ca 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -1,5 +1,6 @@ #include "time_based_cover.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace time_based { diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index e416a5c62b..59fb9f98ed 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -75,7 +75,7 @@ void TLC59208FOutput::setup() { ESP_LOGV(TAG, " Resetting all devices on the bus..."); // Reset all devices on the bus - if (!this->parent_->write_byte(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ[0], TLC59208F_SWRST_SEQ[1])) { + if (this->bus_->write(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "RESET failed"); this->mark_failed(); return; diff --git a/esphome/components/tlc59208f/tlc59208f_output.h b/esphome/components/tlc59208f/tlc59208f_output.h index 79fccc97f3..68ca8061d7 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.h +++ b/esphome/components/tlc59208f/tlc59208f_output.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/output/float_output.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/components/tlc5947/tlc5947.h b/esphome/components/tlc5947/tlc5947.h index b608b861e7..0eb7f10604 100644 --- a/esphome/components/tlc5947/tlc5947.h +++ b/esphome/components/tlc5947/tlc5947.h @@ -3,8 +3,9 @@ // https://www.ti.com/lit/ds/symlink/tlc5947.pdf #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" +#include namespace esphome { namespace tlc5947 { diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 1f1c0fd301..488f3b6727 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -1,6 +1,7 @@ #include "tm1637.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace tm1637 { @@ -146,16 +147,16 @@ void TM1637Display::update() { float TM1637Display::get_setup_priority() const { return setup_priority::PROCESSOR; } void TM1637Display::bit_delay_() { delayMicroseconds(100); } void TM1637Display::start_() { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); } void TM1637Display::stop_() { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); bit_delay_(); - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); bit_delay_(); - this->dio_pin_->pin_mode(INPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); bit_delay_(); } @@ -189,39 +190,39 @@ bool TM1637Display::send_byte_(uint8_t b) { // 8 Data Bits for (uint8_t i = 0; i < 8; i++) { // CLK low - this->clk_pin_->pin_mode(OUTPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); // Set data bit if (data & 0x01) - this->dio_pin_->pin_mode(INPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); else - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); // CLK high - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); data = data >> 1; } // Wait for acknowledge // CLK to zero - this->clk_pin_->pin_mode(OUTPUT); - this->dio_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); // CLK to high - this->clk_pin_->pin_mode(INPUT); + this->clk_pin_->pin_mode(gpio::FLAG_INPUT); this->bit_delay_(); uint8_t ack = this->dio_pin_->digital_read(); if (ack == 0) { - this->dio_pin_->pin_mode(OUTPUT); + this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); } this->bit_delay_(); - this->clk_pin_->pin_mode(OUTPUT); + this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT); this->bit_delay_(); return ack; @@ -233,7 +234,7 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { for (; *str != '\0'; str++) { uint8_t data = TM1637_UNKNOWN_CHAR; if (*str >= ' ' && *str <= '~') - data = pgm_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); + data = progmem_read_byte(&TM1637_ASCII_TO_RAW[*str - ' ']); if (data == TM1637_UNKNOWN_CHAR) { ESP_LOGW(TAG, "Encountered character '%c' with no TM1637 representation while translating string!", *str); diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index 003344eae9..63b30ac13e 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -2,7 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index d06c1bedde..9d2b17afdc 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -27,12 +27,15 @@ TM1651_BRIGHTNESS_OPTIONS = { 3: TM1651Display.TM1651_BRIGHTNESS_HIGH, } -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(TM1651Display), - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, - cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TM1651Display), + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_schema, + cv.Required(CONF_DIO_PIN): pins.internal_gpio_output_pin_schema, + } + ), + cv.only_with_arduino, ) validate_level_percent = cv.All(cv.int_range(min=0, max=100)) diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index 3689ef5894..c6bb1bc025 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -1,3 +1,5 @@ +#ifdef USE_ARDUINO + #include "tm1651.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -88,3 +90,5 @@ uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) { } // namespace tm1651 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index a18519bf41..72849bc8eb 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -1,9 +1,11 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/automation.h" #include @@ -13,8 +15,8 @@ namespace tm1651 { class TM1651Display : public Component { public: - void set_clk_pin(GPIOPin *pin) { clk_pin_ = pin; } - void set_dio_pin(GPIOPin *pin) { dio_pin_ = pin; } + void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; } + void set_dio_pin(InternalGPIOPin *pin) { dio_pin_ = pin; } void setup() override; void dump_config() override; @@ -28,8 +30,8 @@ class TM1651Display : public Component { protected: std::unique_ptr battery_display_; - GPIOPin *clk_pin_; - GPIOPin *dio_pin_; + InternalGPIOPin *clk_pin_; + InternalGPIOPin *dio_pin_; bool is_on_ = true; uint8_t brightness_; @@ -83,3 +85,5 @@ template class TurnOffAction : public Action, public Pare } // namespace tm1651 } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/tmp102/tmp102.cpp b/esphome/components/tmp102/tmp102.cpp index 7b3dcad4aa..f6bb9a05c0 100644 --- a/esphome/components/tmp102/tmp102.cpp +++ b/esphome/components/tmp102/tmp102.cpp @@ -1,5 +1,6 @@ #include "tmp102.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tmp102 { @@ -28,10 +29,16 @@ void TMP102Component::dump_config() { void TMP102Component::update() { uint16_t raw_temperature; - if (!this->read_byte_16(TMP102_REGISTER_TEMPERATURE, &raw_temperature, 50)) { + if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) { this->status_set_warning(); return; } + delay(50); // NOLINT + if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + raw_temperature = i2c::i2ctohs(raw_temperature); raw_temperature = raw_temperature >> 4; float temperature = raw_temperature * TMP102_CONVERSION_FACTOR; diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index cabfdad41d..4ba591f9c4 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -1,5 +1,6 @@ #include "tof10120_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" // Very basic support for TOF10120 distance sensor @@ -31,7 +32,12 @@ void TOF10120Sensor::update() { } uint8_t data[2]; - if (!this->read_bytes(TOF10120_DISTANCE_REGISTER, data, 2, TOF10120_DEFAULT_DELAY)) { + if (this->write(&TOF10120_DISTANCE_REGISTER, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + delay(TOF10120_DEFAULT_DELAY); + if (this->read(data, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Communication with TOF10120 failed on read"); this->status_set_warning(); return; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 81ed5ddce4..25528abbe1 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -127,7 +127,7 @@ void ToshibaClimate::setup() { this->fan_modes_ = this->toshiba_fan_modes_(); this->swing_modes_ = this->toshiba_swing_modes_(); // Never send nan to HA - if (isnan(this->target_temperature)) + if (std::isnan(this->target_temperature)) this->target_temperature = 24; } diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 83333acab7..178dc7cbe0 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -7,7 +7,7 @@ namespace total_daily_energy { static const char *const TAG = "total_daily_energy"; void TotalDailyEnergy::setup() { - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); float recovered; if (this->pref_.load(&recovered)) { @@ -52,7 +52,7 @@ void TotalDailyEnergy::publish_state_and_save(float state) { } void TotalDailyEnergy::process_new_state_(float state) { - if (isnan(state)) + if (std::isnan(state)) return; const uint32_t now = millis(); const float old_state = this->last_power_state_; diff --git a/esphome/components/total_daily_energy/total_daily_energy.h b/esphome/components/total_daily_energy/total_daily_energy.h index fd71b8decc..9d2396d6e3 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.h +++ b/esphome/components/total_daily_energy/total_daily_energy.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/core/preferences.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/time/real_time_clock.h" diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 1785fa46b4..7755437de2 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -1,5 +1,6 @@ #include "tsl2591.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace tsl2591 { @@ -362,7 +363,7 @@ float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infr // For the curious "cpl" is counts per lux, a term used in AMS application notes. float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_); float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl; - return max(lux, 0.0F); + return std::max(lux, 0.0F); } } // namespace tsl2591 diff --git a/esphome/components/ttp229_bsf/ttp229_bsf.h b/esphome/components/ttp229_bsf/ttp229_bsf.h index d73e4fb185..59749a4fa7 100644 --- a/esphome/components/ttp229_bsf/ttp229_bsf.h +++ b/esphome/components/ttp229_bsf/ttp229_bsf.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/binary_sensor/binary_sensor.h" namespace esphome { diff --git a/esphome/components/ttp229_lsf/ttp229_lsf.cpp b/esphome/components/ttp229_lsf/ttp229_lsf.cpp index 6e3e68ea7a..21c7b02740 100644 --- a/esphome/components/ttp229_lsf/ttp229_lsf.cpp +++ b/esphome/components/ttp229_lsf/ttp229_lsf.cpp @@ -8,7 +8,8 @@ static const char *const TAG = "ttp229_lsf"; void TTP229LSFComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up ttp229..."); - if (!this->parent_->raw_request_from(this->address_, 2)) { + uint8_t data[2]; + if (this->read(data, 2) != i2c::ERROR_OK) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); return; @@ -28,10 +29,11 @@ void TTP229LSFComponent::dump_config() { } void TTP229LSFComponent::loop() { uint16_t touched = 0; - if (!this->parent_->raw_receive_16(this->address_, &touched, 1)) { + if (this->read(reinterpret_cast(&touched), 2) != i2c::ERROR_OK) { this->status_set_warning(); return; } + touched = i2c::i2ctohs(touched); this->status_clear_warning(); touched = reverse_bits_16(touched); for (auto *channel : this->channels_) { diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 293bfb4f88..39d4203684 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -169,7 +169,7 @@ void TuyaClimate::compute_target_temperature_() { } void TuyaClimate::compute_state_() { - if (isnan(this->current_temperature) || isnan(this->target_temperature)) { + if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) { // if any control parameters are nan, go to OFF action (not IDLE!) this->switch_to_action_(climate::CLIMATE_ACTION_OFF); return; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index e42c74005e..bbbc9274c3 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -1,7 +1,8 @@ #include "tuya.h" #include "esphome/core/log.h" -#include "esphome/core/util.h" +#include "esphome/components/network/util.h" #include "esphome/core/helpers.h" +#include "esphome/core/util.h" namespace esphome { namespace tuya { @@ -389,7 +390,7 @@ void Tuya::send_empty_command_(TuyaCommandType command) { void Tuya::send_wifi_status_() { uint8_t status = 0x02; - if (network_is_connected()) { + if (network::is_connected()) { status = 0x03; // Protocol version 3 also supports specifying when connected to "the cloud" diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index ceb9b88d8d..84df82b5e6 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -33,9 +33,7 @@ CONFIG_SCHEMA = cv.Schema( accuracy_decimals=1, state_class=STATE_CLASS_NONE, ), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index f48e29521c..6e0b6343d1 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -20,7 +20,7 @@ void Tx20Component::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.reset(); - this->pin_->attach_interrupt(Tx20ComponentStore::gpio_intr, &this->store_, CHANGE); + this->pin_->attach_interrupt(Tx20ComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } void Tx20Component::dump_config() { ESP_LOGCONFIG(TAG, "Tx20:"); @@ -140,8 +140,8 @@ void Tx20Component::decode_and_publish_() { } } -void ICACHE_RAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { - arg->pin_state = arg->pin->digital_read(); +void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { + arg->pin_state = arg->pin.digital_read(); const uint32_t now = micros(); if (!arg->start_time) { // only detect a start if the bit is high @@ -183,7 +183,7 @@ void ICACHE_RAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { arg->start_time = now; arg->buffer_index++; } -void ICACHE_RAM_ATTR Tx20ComponentStore::reset() { +void IRAM_ATTR Tx20ComponentStore::reset() { tx20_available = false; buffer_index = 0; spent_time = 0; diff --git a/esphome/components/tx20/tx20.h b/esphome/components/tx20/tx20.h index 56cb723fa1..1c617d0674 100644 --- a/esphome/components/tx20/tx20.h +++ b/esphome/components/tx20/tx20.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -15,7 +15,7 @@ struct Tx20ComponentStore { volatile uint32_t spent_time; volatile bool tx20_available; volatile bool pin_state; - ISRInternalGPIOPin *pin; + ISRInternalGPIOPin pin; void reset(); static void gpio_intr(Tx20ComponentStore *arg); @@ -27,7 +27,7 @@ class Tx20Component : public Component { /// Get the textual representation of the wind direction ('N', 'SSE', ..). std::string get_wind_cardinal_direction() const; - void set_pin(GPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void set_wind_speed_sensor(sensor::Sensor *wind_speed_sensor) { wind_speed_sensor_ = wind_speed_sensor; } void set_wind_direction_degrees_sensor(sensor::Sensor *wind_direction_degrees_sensor) { wind_direction_degrees_sensor_ = wind_direction_degrees_sensor; @@ -42,7 +42,7 @@ class Tx20Component : public Component { void decode_and_publish_(); std::string wind_cardinal_direction_; - GPIOPin *pin_; + InternalGPIOPin *pin_; sensor::Sensor *wind_speed_sensor_; sensor::Sensor *wind_direction_degrees_sensor_; Tx20ComponentStore store_; diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index d2fcac2cb6..35af3eedf7 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -7,6 +7,7 @@ from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, CONF_ID, + CONF_NUMBER, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, @@ -18,7 +19,16 @@ from esphome.core import CORE CODEOWNERS = ["@esphome/core"] uart_ns = cg.esphome_ns.namespace("uart") -UARTComponent = uart_ns.class_("UARTComponent", cg.Component) +UARTComponent = uart_ns.class_("UARTComponent") + +IDFUARTComponent = uart_ns.class_("IDFUARTComponent", UARTComponent, cg.Component) +ESP32ArduinoUARTComponent = uart_ns.class_( + "ESP32ArduinoUARTComponent", UARTComponent, cg.Component +) +ESP8266UartComponent = uart_ns.class_( + "ESP8266UartComponent", UARTComponent, cg.Component +) + UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) MULTI_CONF = True @@ -37,12 +47,23 @@ def validate_raw_data(value): def validate_rx_pin(value): - value = pins.input_pin(value) - if CORE.is_esp8266 and value >= 16: + value = pins.internal_gpio_input_pin_schema(value) + if CORE.is_esp8266 and value[CONF_NUMBER] >= 16: raise cv.Invalid("Pins GPIO16 and GPIO17 cannot be used as RX pins on ESP8266.") return value +def _uart_declare_type(value): + if CORE.is_esp8266: + return cv.declare_id(ESP8266UartComponent)(value) + if CORE.is_esp32: + if CORE.using_arduino: + return cv.declare_id(ESP32ArduinoUARTComponent)(value) + if CORE.using_esp_idf: + return cv.declare_id(IDFUARTComponent)(value) + raise NotImplementedError + + UARTParityOptions = uart_ns.enum("UARTParityOptions") UART_PARITY_OPTIONS = { "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE, @@ -57,19 +78,19 @@ CONF_PARITY = "parity" CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.GenerateID(): cv.declare_id(UARTComponent), + cv.GenerateID(): _uart_declare_type, cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), - cv.Optional(CONF_TX_PIN): pins.output_pin, + cv.Optional(CONF_TX_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_RX_PIN): validate_rx_pin, cv.Optional(CONF_RX_BUFFER_SIZE, default=256): cv.validate_bytes, - cv.SplitDefault(CONF_INVERT, esp32=False): cv.All( - cv.only_on_esp32, cv.boolean - ), cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True), cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(min=5, max=8), cv.Optional(CONF_PARITY, default="NONE"): cv.enum( UART_PARITY_OPTIONS, upper=True ), + cv.Optional(CONF_INVERT): cv.invalid( + "This option has been removed. Please instead use invert in the tx/rx pin schemas." + ), } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), @@ -84,12 +105,12 @@ async def to_code(config): cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) if CONF_TX_PIN in config: - cg.add(var.set_tx_pin(config[CONF_TX_PIN])) + tx_pin = await cg.gpio_pin_expression(config[CONF_TX_PIN]) + cg.add(var.set_tx_pin(tx_pin)) if CONF_RX_PIN in config: - cg.add(var.set_rx_pin(config[CONF_RX_PIN])) + rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN]) + cg.add(var.set_rx_pin(rx_pin)) cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE])) - if CONF_INVERT in config: - cg.add(var.set_invert(config[CONF_INVERT])) cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_parity(config[CONF_PARITY])) diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 8cc8a47b14..22a22e2772 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -4,62 +4,28 @@ #include "esphome/core/application.h" #include "esphome/core/defines.h" -#ifdef USE_LOGGER -#include "esphome/components/logger/logger.h" -#endif - namespace esphome { namespace uart { static const char *const TAG = "uart"; -size_t UARTComponent::write(uint8_t data) { - this->write_byte(data); - return 1; -} -int UARTComponent::read() { - uint8_t data; - if (!this->read_byte(&data)) - return -1; - return data; -} -int UARTComponent::peek() { - uint8_t data; - if (!this->peek_byte(&data)) - return -1; - return data; -} - -void UARTComponent::check_logger_conflict_() { -#ifdef USE_LOGGER - if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { - return; - } - - if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { - ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " - "disable logging over the serial port by setting logger->baud_rate to 0."); - } -#endif -} - void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity, uint8_t data_bits) { - if (this->parent_->baud_rate_ != baud_rate) { + if (this->parent_->get_baud_rate() != baud_rate) { ESP_LOGE(TAG, " Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate, - this->parent_->baud_rate_); + this->parent_->get_baud_rate()); } - if (this->parent_->stop_bits_ != stop_bits) { + if (this->parent_->get_stop_bits() != stop_bits) { ESP_LOGE(TAG, " Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits, - this->parent_->stop_bits_); + this->parent_->get_stop_bits()); } - if (this->parent_->data_bits_ != data_bits) { + if (this->parent_->get_data_bits() != data_bits) { ESP_LOGE(TAG, " Invalid number of data bits: Integration requested %u data bits but you have %u!", data_bits, - this->parent_->data_bits_); + this->parent_->get_data_bits()); } - if (this->parent_->parity_ != parity) { + if (this->parent_->get_parity() != parity) { ESP_LOGE(TAG, " Invalid parity: Integration requested parity %s but you have %s!", - LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->parity_))); + LOG_STR_ARG(parity_to_str(parity)), LOG_STR_ARG(parity_to_str(this->parent_->get_parity()))); } } diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index a79b7b841e..c368f9ed6b 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -1,132 +1,15 @@ #pragma once #include -#include -#include "esphome/core/esphal.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "uart_component.h" namespace esphome { namespace uart { -enum UARTParityOptions { - UART_CONFIG_PARITY_NONE, - UART_CONFIG_PARITY_EVEN, - UART_CONFIG_PARITY_ODD, -}; - -const LogString *parity_to_str(UARTParityOptions parity); - -#ifdef ARDUINO_ARCH_ESP8266 -class ESP8266SoftwareSerial { - public: - void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, uint32_t data_bits, - UARTParityOptions parity, size_t rx_buffer_size); - - uint8_t read_byte(); - uint8_t peek_byte(); - - void flush(); - - void write_byte(uint8_t data); - - int available(); - - GPIOPin *gpio_tx_pin_{nullptr}; - GPIOPin *gpio_rx_pin_{nullptr}; - - protected: - static void gpio_intr(ESP8266SoftwareSerial *arg); - - void wait_(uint32_t *wait, const uint32_t &start); - bool read_bit_(uint32_t *wait, const uint32_t &start); - void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); - - uint32_t bit_time_{0}; - uint8_t *rx_buffer_{nullptr}; - size_t rx_buffer_size_; - volatile size_t rx_in_pos_{0}; - size_t rx_out_pos_{0}; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; - ISRInternalGPIOPin *tx_pin_{nullptr}; - ISRInternalGPIOPin *rx_pin_{nullptr}; -}; -#endif - -class UARTComponent : public Component, public Stream { - public: - void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } - uint32_t get_baud_rate() const { return baud_rate_; } - - uint32_t get_config(); - - void setup() override; - - void dump_config() override; - - void write_byte(uint8_t data); - - void write_array(const uint8_t *data, size_t len); - void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } - - void write_str(const char *str); - - bool peek_byte(uint8_t *data); - - bool read_byte(uint8_t *data); - - bool read_array(uint8_t *data, size_t len); - - int available() override; - - /// Block until all bytes have been written to the UART bus. - void flush() override; - - float get_setup_priority() const override { return setup_priority::BUS; } - - size_t write(uint8_t data) override; - int read() override; - int peek() override; - - void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; } - void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; } - void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } -#ifdef ARDUINO_ARCH_ESP32 - void set_invert(bool invert) { this->invert_ = invert; } -#endif - void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } - void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } - void set_parity(UARTParityOptions parity) { this->parity_ = parity; } - - protected: - void check_logger_conflict_(); - bool check_read_timeout_(size_t len = 1); - friend class UARTDevice; - - HardwareSerial *hw_serial_{nullptr}; -#ifdef ARDUINO_ARCH_ESP8266 - ESP8266SoftwareSerial *sw_serial_{nullptr}; -#endif - optional tx_pin_; - optional rx_pin_; - size_t rx_buffer_size_; -#ifdef ARDUINO_ARCH_ESP32 - bool invert_; -#endif - uint32_t baud_rate_; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; - - private: -#ifdef ARDUINO_ARCH_ESP8266 - static bool serial0InUse; -#endif -}; - -class UARTDevice : public Stream { +class UARTDevice { public: UARTDevice() = default; UARTDevice(UARTComponent *parent) : parent_(parent) {} @@ -155,13 +38,27 @@ class UARTDevice : public Stream { return res; } - int available() override { return this->parent_->available(); } + int available() { return this->parent_->available(); } - void flush() override { return this->parent_->flush(); } + void flush() { return this->parent_->flush(); } - size_t write(uint8_t data) override { return this->parent_->write(data); } - int read() override { return this->parent_->read(); } - int peek() override { return this->parent_->peek(); } + // Compat APIs + int read() { + uint8_t data; + if (!read_byte(&data)) + return -1; + return data; + } + size_t write(uint8_t data) { + write_byte(data); + return 1; + } + int peek() { + uint8_t data; + if (!peek_byte(&data)) + return -1; + return data; + } /// Check that the configuration of the UART bus matches the provided values and otherwise print a warning void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1, diff --git a/esphome/components/uart/uart_component.cpp b/esphome/components/uart/uart_component.cpp new file mode 100644 index 0000000000..09b8c975ab --- /dev/null +++ b/esphome/components/uart/uart_component.cpp @@ -0,0 +1,24 @@ +#include "uart_component.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart"; + +bool UARTComponent::check_read_timeout_(size_t len) { + if (this->available() >= int(len)) + return true; + + uint32_t start_time = millis(); + while (this->available() < int(len)) { + if (millis() - start_time > 100) { + ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); + return false; + } + yield(); + } + return true; +} + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h new file mode 100644 index 0000000000..de85cd2ca3 --- /dev/null +++ b/esphome/components/uart/uart_component.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +enum UARTParityOptions { + UART_CONFIG_PARITY_NONE, + UART_CONFIG_PARITY_EVEN, + UART_CONFIG_PARITY_ODD, +}; + +const LogString *parity_to_str(UARTParityOptions parity); + +class UARTComponent { + public: + void write_array(const std::vector &data) { this->write_array(&data[0], data.size()); } + void write_byte(uint8_t data) { this->write_array(&data, 1); }; + void write_str(const char *str) { + const auto *data = reinterpret_cast(str); + this->write_array(data, strlen(str)); + }; + + virtual void write_array(const uint8_t *data, size_t len) = 0; + + bool read_byte(uint8_t *data) { return this->read_array(data, 1); }; + virtual bool peek_byte(uint8_t *data) = 0; + virtual bool read_array(uint8_t *data, size_t len) = 0; + + /// Return available number of bytes. + virtual int available() = 0; + /// Block until all bytes have been written to the UART bus. + virtual void flush() = 0; + + void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } + void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } + void set_rx_buffer_size(size_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; } + + void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; } + uint8_t get_stop_bits() const { return this->stop_bits_; } + void set_data_bits(uint8_t data_bits) { this->data_bits_ = data_bits; } + uint8_t get_data_bits() const { return this->data_bits_; } + void set_parity(UARTParityOptions parity) { this->parity_ = parity; } + UARTParityOptions get_parity() const { return this->parity_; } + void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } + + protected: + virtual void check_logger_conflict() = 0; + bool check_read_timeout_(size_t len = 1); + + InternalGPIOPin *tx_pin_; + InternalGPIOPin *rx_pin_; + size_t rx_buffer_size_; + uint32_t baud_rate_; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; +}; + +} // namespace uart +} // namespace esphome diff --git a/esphome/components/uart/uart_esp32.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp similarity index 64% rename from esphome/components/uart/uart_esp32.cpp rename to esphome/components/uart/uart_component_esp32_arduino.cpp index db2757780e..1b1ce382f2 100644 --- a/esphome/components/uart/uart_esp32.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -1,13 +1,17 @@ -#ifdef ARDUINO_ARCH_ESP32 -#include "uart.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "uart_component_esp32_arduino.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif namespace esphome { namespace uart { -static const char *const TAG = "uart_esp32"; +static const char *const TAG = "uart.arduino_esp32"; static const uint32_t UART_PARITY_EVEN = 0 << 0; static const uint32_t UART_PARITY_ODD = 1 << 0; @@ -20,7 +24,7 @@ static const uint32_t UART_NB_STOP_BIT_1 = 1 << 4; static const uint32_t UART_NB_STOP_BIT_2 = 3 << 4; static const uint32_t UART_TICK_APB_CLOCK = 1 << 27; -uint32_t UARTComponent::get_config() { +uint32_t ESP32ArduinoUARTComponent::get_config() { uint32_t config = 0; /* @@ -67,71 +71,63 @@ uint32_t UARTComponent::get_config() { return config; } -void UARTComponent::setup() { +void ESP32ArduinoUARTComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up UART..."); // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. + bool is_default_tx, is_default_rx; #ifdef CONFIG_IDF_TARGET_ESP32C3 - if (this->tx_pin_.value_or(21) == 21 && this->rx_pin_.value_or(20) == 20) { + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 21; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 20; #else - if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { + is_default_tx = tx_pin_ == nullptr || tx_pin_->get_pin() == 1; + is_default_rx = rx_pin_ == nullptr || rx_pin_->get_pin() == 3; #endif + if (is_default_tx && is_default_rx) { this->hw_serial_ = &Serial; } else { static uint8_t next_uart_num = 1; this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory) } - int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; - int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; - this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, this->invert_); + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + bool invert = false; + if (tx_pin_ != nullptr && tx_pin_->is_inverted()) + invert = true; + if (rx_pin_ != nullptr && rx_pin_->is_inverted()) + invert = true; + this->hw_serial_->begin(this->baud_rate_, get_config(), rx, tx, invert); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } -void UARTComponent::dump_config() { +void ESP32ArduinoUARTComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - if (this->tx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " TX Pin: GPIO%d", *this->tx_pin_); - } - if (this->rx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); - this->check_logger_conflict_(); + this->check_logger_conflict(); } -void UARTComponent::write_byte(uint8_t data) { - this->hw_serial_->write(data); - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); -} -void UARTComponent::write_array(const uint8_t *data, size_t len) { +void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { this->hw_serial_->write(data, len); for (size_t i = 0; i < len; i++) { ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); } } -void UARTComponent::write_str(const char *str) { - this->hw_serial_->write(str); - ESP_LOGVV(TAG, " Wrote \"%s\"", str); -} -bool UARTComponent::read_byte(uint8_t *data) { - if (!this->check_read_timeout_()) - return false; - *data = this->hw_serial_->read(); - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); - return true; -} -bool UARTComponent::peek_byte(uint8_t *data) { +bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; *data = this->hw_serial_->peek(); return true; } -bool UARTComponent::read_array(uint8_t *data, size_t len) { +bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; this->hw_serial_->readBytes(data, len); @@ -141,26 +137,25 @@ bool UARTComponent::read_array(uint8_t *data, size_t len) { return true; } -bool UARTComponent::check_read_timeout_(size_t len) { - if (this->available() >= len) - return true; - - uint32_t start_time = millis(); - while (this->available() < len) { - if (millis() - start_time > 1000) { - ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); - return false; - } - yield(); - } - return true; -} -int UARTComponent::available() { return this->hw_serial_->available(); } -void UARTComponent::flush() { +int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } +void ESP32ArduinoUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); this->hw_serial_->flush(); } +void ESP32ArduinoUARTComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; + } + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif +} + } // namespace uart } // namespace esphome -#endif // ARDUINO_ARCH_ESP32 +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_component_esp32_arduino.h b/esphome/components/uart/uart_component_esp32_arduino.h new file mode 100644 index 0000000000..c6f445ff12 --- /dev/null +++ b/esphome/components/uart/uart_component_esp32_arduino.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP32ArduinoUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/uart/uart_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp similarity index 60% rename from esphome/components/uart/uart_esp8266.cpp rename to esphome/components/uart/uart_component_esp8266.cpp index a74f2601e4..2188a4a4bc 100644 --- a/esphome/components/uart/uart_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -1,9 +1,9 @@ -#ifdef ARDUINO_ARCH_ESP8266 -#include "uart.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" +#ifdef USE_ESP8266 +#include "uart_component_esp8266.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" @@ -12,10 +12,10 @@ namespace esphome { namespace uart { -static const char *const TAG = "uart_esp8266"; -bool UARTComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static const char *const TAG = "uart.arduino_esp8266"; +bool ESP8266UartComponent::serial0InUse = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -uint32_t UARTComponent::get_config() { +uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; if (this->parity_ == UART_CONFIG_PARITY_NONE) @@ -48,15 +48,15 @@ uint32_t UARTComponent::get_config() { return config; } -void UARTComponent::setup() { +void ESP8266UartComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up UART bus..."); // Use Arduino HardwareSerial UARTs if all used pins match the ones // preconfigured by the platform. For example if RX disabled but TX pin // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); - if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(1) == 1 && - this->rx_pin_.value_or(3) == 3 + if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER // we will use UART0 if logger isn't using it in swapped mode && (logger::global_logger->get_hw_serial() == nullptr || @@ -66,9 +66,9 @@ void UARTComponent::setup() { this->hw_serial_ = &Serial; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - UARTComponent::serial0InUse = true; - } else if (!UARTComponent::serial0InUse && this->tx_pin_.value_or(15) == 15 && - this->rx_pin_.value_or(13) == 13 + ESP8266UartComponent::serial0InUse = true; + } else if (!ESP8266UartComponent::serial0InUse && (tx_pin_ == nullptr || tx_pin_->get_pin() == 15) && + (rx_pin_ == nullptr || rx_pin_->get_pin() == 13) #ifdef USE_LOGGER // we will use UART0 swapped if logger isn't using it in regular mode && (logger::global_logger->get_hw_serial() == nullptr || @@ -79,27 +79,23 @@ void UARTComponent::setup() { this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); - UARTComponent::serial0InUse = true; - } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { + ESP8266UartComponent::serial0InUse = true; + } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); } else { this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT - int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; - int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; - this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, + this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, this->rx_buffer_size_); } } -void UARTComponent::dump_config() { +void ESP8266UartComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus:"); - if (this->tx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " TX Pin: GPIO%d", *this->tx_pin_); - } - if (this->rx_pin_.has_value()) { - ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); // NOLINT } ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); @@ -111,18 +107,23 @@ void UARTComponent::dump_config() { } else { ESP_LOGCONFIG(TAG, " Using software serial"); } - this->check_logger_conflict_(); + this->check_logger_conflict(); } -void UARTComponent::write_byte(uint8_t data) { - if (this->hw_serial_ != nullptr) { - this->hw_serial_->write(data); - } else { - this->sw_serial_->write_byte(data); +void ESP8266UartComponent::check_logger_conflict() { +#ifdef USE_LOGGER + if (this->hw_serial_ == nullptr || logger::global_logger->get_baud_rate() == 0) { + return; } - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); + + if (this->hw_serial_ == logger::global_logger->get_hw_serial()) { + ESP_LOGW(TAG, " You're using the same serial port for logging and the UART component. Please " + "disable logging over the serial port by setting logger->baud_rate to 0."); + } +#endif } -void UARTComponent::write_array(const uint8_t *data, size_t len) { + +void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) { if (this->hw_serial_ != nullptr) { this->hw_serial_->write(data, len); } else { @@ -133,28 +134,7 @@ void UARTComponent::write_array(const uint8_t *data, size_t len) { ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); } } -void UARTComponent::write_str(const char *str) { - if (this->hw_serial_ != nullptr) { - this->hw_serial_->write(str); - } else { - const auto *data = reinterpret_cast(str); - for (size_t i = 0; data[i] != 0; i++) - this->sw_serial_->write_byte(data[i]); - } - ESP_LOGVV(TAG, " Wrote \"%s\"", str); -} -bool UARTComponent::read_byte(uint8_t *data) { - if (!this->check_read_timeout_()) - return false; - if (this->hw_serial_ != nullptr) { - *data = this->hw_serial_->read(); - } else { - *data = this->sw_serial_->read_byte(); - } - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(*data), *data); - return true; -} -bool UARTComponent::peek_byte(uint8_t *data) { +bool ESP8266UartComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; if (this->hw_serial_ != nullptr) { @@ -164,7 +144,7 @@ bool UARTComponent::peek_byte(uint8_t *data) { } return true; } -bool UARTComponent::read_array(uint8_t *data, size_t len) { +bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; if (this->hw_serial_ != nullptr) { @@ -179,28 +159,14 @@ bool UARTComponent::read_array(uint8_t *data, size_t len) { return true; } -bool UARTComponent::check_read_timeout_(size_t len) { - if (this->available() >= int(len)) - return true; - - uint32_t start_time = millis(); - while (this->available() < int(len)) { - if (millis() - start_time > 100) { - ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available()); - return false; - } - yield(); - } - return true; -} -int UARTComponent::available() { +int ESP8266UartComponent::available() { if (this->hw_serial_ != nullptr) { return this->hw_serial_->available(); } else { return this->sw_serial_->available(); } } -void UARTComponent::flush() { +void ESP8266UartComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); if (this->hw_serial_ != nullptr) { this->hw_serial_->flush(); @@ -208,32 +174,31 @@ void UARTComponent::flush() { this->sw_serial_->flush(); } } -void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits, - uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size) { +void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, + uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity, + size_t rx_buffer_size) { this->bit_time_ = F_CPU / baud_rate; this->rx_buffer_size_ = rx_buffer_size; this->stop_bits_ = stop_bits; this->data_bits_ = data_bits; this->parity_ = parity; - if (tx_pin != -1) { - auto pin = GPIOPin(tx_pin, OUTPUT); - this->gpio_tx_pin_ = &pin; - pin.setup(); - this->tx_pin_ = pin.to_isr(); - this->tx_pin_->digital_write(true); + if (tx_pin != nullptr) { + gpio_tx_pin_ = tx_pin; + gpio_tx_pin_->setup(); + tx_pin_ = gpio_tx_pin_->to_isr(); + tx_pin_.digital_write(true); } - if (rx_pin != -1) { - auto pin = GPIOPin(rx_pin, INPUT); - pin.setup(); - this->gpio_rx_pin_ = &pin; - this->rx_pin_ = pin.to_isr(); - this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT - pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); + if (rx_pin != nullptr) { + gpio_rx_pin_ = rx_pin; + gpio_rx_pin_->setup(); + rx_pin_ = gpio_rx_pin_->to_isr(); + rx_buffer_ = new uint8_t[this->rx_buffer_size_]; // NOLINT + gpio_rx_pin_->attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); } } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { +void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; - const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + const uint32_t start = arch_get_cpu_cycle_count(); uint8_t rec = 0; // Manually unroll the loop for (int i = 0; i < arg->data_bits_; i++) @@ -254,10 +219,10 @@ void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg arg->rx_buffer_[arg->rx_in_pos_] = rec; arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; // Clear RX pin so that the interrupt doesn't re-trigger right away again. - arg->rx_pin_->clear_interrupt(); + arg->rx_pin_.clear_interrupt(); } -void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { - if (this->tx_pin_ == nullptr) { +void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { + if (this->gpio_tx_pin_ == nullptr) { ESP_LOGE(TAG, "UART doesn't have TX pins set!"); return; } @@ -273,7 +238,7 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { { InterruptLock lock; uint32_t wait = this->bit_time_; - const uint32_t start = ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance) + const uint32_t start = arch_get_cpu_cycle_count(); // Start bit this->write_bit_(false, &wait, start); for (int i = 0; i < this->data_bits_; i++) { @@ -290,17 +255,17 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { this->wait_(&wait, start); } } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { - while (ESP.getCycleCount() - start < *wait) // NOLINT(readability-static-accessed-through-instance) +void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { + while (arch_get_cpu_cycle_count() - start < *wait) ; *wait += this->bit_time_; } -bool ICACHE_RAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { +bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) { this->wait_(wait, start); - return this->rx_pin_->digital_read(); + return this->rx_pin_.digital_read(); } -void ICACHE_RAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { - this->tx_pin_->digital_write(bit); +void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) { + this->tx_pin_.digital_write(bit); this->wait_(wait, start); } uint8_t ESP8266SoftwareSerial::read_byte() { @@ -327,4 +292,4 @@ int ESP8266SoftwareSerial::available() { } // namespace uart } // namespace esphome -#endif // ARDUINO_ARCH_ESP8266 +#endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp8266.h b/esphome/components/uart/uart_component_esp8266.h new file mode 100644 index 0000000000..921d77e4f3 --- /dev/null +++ b/esphome/components/uart/uart_component_esp8266.h @@ -0,0 +1,79 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include +#include +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class ESP8266SoftwareSerial { + public: + void setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate, uint8_t stop_bits, + uint32_t data_bits, UARTParityOptions parity, size_t rx_buffer_size); + + uint8_t read_byte(); + uint8_t peek_byte(); + + void flush(); + + void write_byte(uint8_t data); + + int available(); + + protected: + static void gpio_intr(ESP8266SoftwareSerial *arg); + + void wait_(uint32_t *wait, const uint32_t &start); + bool read_bit_(uint32_t *wait, const uint32_t &start); + void write_bit_(bool bit, uint32_t *wait, const uint32_t &start); + + uint32_t bit_time_{0}; + uint8_t *rx_buffer_{nullptr}; + size_t rx_buffer_size_; + volatile size_t rx_in_pos_{0}; + size_t rx_out_pos_{0}; + uint8_t stop_bits_; + uint8_t data_bits_; + UARTParityOptions parity_; + InternalGPIOPin *gpio_tx_pin_{nullptr}; + ISRInternalGPIOPin tx_pin_; + InternalGPIOPin *gpio_rx_pin_{nullptr}; + ISRInternalGPIOPin rx_pin_; +}; + +class ESP8266UartComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + uint32_t get_config(); + + protected: + void check_logger_conflict() override; + + HardwareSerial *hw_serial_{nullptr}; + ESP8266SoftwareSerial *sw_serial_{nullptr}; + + private: + static bool serial0InUse; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp new file mode 100644 index 0000000000..3d4a634a72 --- /dev/null +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -0,0 +1,201 @@ +#ifdef USE_ESP_IDF + +#include "uart_component_esp_idf.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_LOGGER +#include "esphome/components/logger/logger.h" +#endif + +namespace esphome { +namespace uart { +static const char *const TAG = "uart.idf"; + +uart_config_t IDFUARTComponent::get_config() { + uart_parity_t parity = UART_PARITY_DISABLE; + if (this->parity_ == UART_CONFIG_PARITY_EVEN) + parity = UART_PARITY_EVEN; + else if (this->parity_ == UART_CONFIG_PARITY_ODD) + parity = UART_PARITY_ODD; + + uart_word_length_t data_bits; + switch (this->data_bits_) { + case 5: + data_bits = UART_DATA_5_BITS; + break; + case 6: + data_bits = UART_DATA_6_BITS; + break; + case 7: + data_bits = UART_DATA_7_BITS; + break; + case 8: + data_bits = UART_DATA_8_BITS; + break; + default: + data_bits = UART_DATA_BITS_MAX; + break; + } + + uart_config_t uart_config; + uart_config.baud_rate = this->baud_rate_; + uart_config.data_bits = data_bits; + uart_config.parity = parity; + uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_APB; + uart_config.rx_flow_ctrl_thresh = 122; + + return uart_config; +} + +void IDFUARTComponent::setup() { + static uint8_t next_uart_num = 0; +#ifdef USE_LOGGER + if (logger::global_logger->get_uart_num() == next_uart_num) + next_uart_num++; +#endif + if (next_uart_num >= UART_NUM_MAX) { + ESP_LOGW(TAG, "Maximum number of UART components created already."); + this->mark_failed(); + return; + } + this->uart_num_ = next_uart_num++; + ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_); + + this->lock_ = xSemaphoreCreateMutex(); + + xSemaphoreTake(this->lock_, portMAX_DELAY); + + uart_config_t uart_config = this->get_config(); + esp_err_t err = uart_param_config(this->uart_num_, &uart_config); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + err = uart_driver_install(this->uart_num_, this->rx_buffer_size_, 0, 0, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; + int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; + + err = uart_set_pin(this->uart_num_, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + uint32_t invert = 0; + if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) + invert |= UART_SIGNAL_TXD_INV; + if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) + invert |= UART_SIGNAL_RXD_INV; + + err = uart_set_line_inverse(this->uart_num_, invert); + if (err != ESP_OK) { + ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; + } + + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::dump_config() { + ESP_LOGCONFIG(TAG, "UART Bus:"); + ESP_LOGCONFIG(TAG, " Number: %u", this->uart_num_); + LOG_PIN(" TX Pin: ", tx_pin_); + LOG_PIN(" RX Pin: ", rx_pin_); + if (this->rx_pin_ != nullptr) { + ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_); + } + ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); + ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_); + ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_))); + ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_); + this->check_logger_conflict(); +} + +void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_write_bytes(this->uart_num_, data, len); + xSemaphoreGive(this->lock_); + for (size_t i = 0; i < len; i++) { + ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + } +} +bool IDFUARTComponent::peek_byte(uint8_t *data) { + if (!this->check_read_timeout_()) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) + *data = this->peek_byte_; + else { + int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_RATE_MS); + if (len == 0) { + *data = 0; + } else { + this->has_peek_ = true; + this->peek_byte_ = *data; + } + } + xSemaphoreGive(this->lock_); + return true; +} +bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { + size_t length_to_read = len; + if (!this->check_read_timeout_(len)) + return false; + xSemaphoreTake(this->lock_, portMAX_DELAY); + if (this->has_peek_) { + length_to_read--; + *data = this->peek_byte_; + data++; + this->has_peek_ = false; + } + if (length_to_read > 0) + uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); + + xSemaphoreGive(this->lock_); + for (size_t i = 0; i < len; i++) { + ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + } + + return true; +} + +int IDFUARTComponent::available() { + size_t available; + + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_get_buffered_data_len(this->uart_num_, &available); + if (this->has_peek_) + available++; + xSemaphoreGive(this->lock_); + + return available; +} + +void IDFUARTComponent::flush() { + ESP_LOGVV(TAG, " Flushing..."); + xSemaphoreTake(this->lock_, portMAX_DELAY); + uart_wait_tx_done(this->uart_num_, portMAX_DELAY); + xSemaphoreGive(this->lock_); +} + +void IDFUARTComponent::check_logger_conflict() {} + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h new file mode 100644 index 0000000000..68cceafda2 --- /dev/null +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -0,0 +1,39 @@ +#pragma once + +#ifdef USE_ESP_IDF + +#include +#include "esphome/core/component.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +class IDFUARTComponent : public UARTComponent, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + void write_array(const uint8_t *data, size_t len) override; + + bool peek_byte(uint8_t *data) override; + bool read_array(uint8_t *data, size_t len) override; + + int available() override; + void flush() override; + + protected: + void check_logger_conflict() override; + uart_port_t uart_num_; + uart_config_t get_config(); + SemaphoreHandle_t lock_; + + bool has_peek_{false}; + uint8_t peek_byte_; +}; + +} // namespace uart +} // namespace esphome + +#endif // USE_ESP_IDF diff --git a/esphome/components/uln2003/uln2003.h b/esphome/components/uln2003/uln2003.h index 4bcf1e88e3..4f559ed9a0 100644 --- a/esphome/components/uln2003/uln2003.h +++ b/esphome/components/uln2003/uln2003.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/stepper/stepper.h" namespace esphome { diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index e53cd7cf7a..9f47f9f6b9 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -1,5 +1,6 @@ #include "ultrasonic_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" namespace esphome { namespace ultrasonic { @@ -11,22 +12,31 @@ void UltrasonicSensorComponent::setup() { this->trigger_pin_->setup(); this->trigger_pin_->digital_write(false); this->echo_pin_->setup(); + // isr is faster to access + echo_isr_ = echo_pin_->to_isr(); } void UltrasonicSensorComponent::update() { this->trigger_pin_->digital_write(true); delayMicroseconds(this->pulse_time_us_); this->trigger_pin_->digital_write(false); - uint32_t time = pulseIn( // NOLINT - this->echo_pin_->get_pin(), uint8_t(!this->echo_pin_->is_inverted()), this->timeout_us_); + const uint32_t start = micros(); + while (micros() - start < timeout_us_ && echo_isr_.digital_read()) + ; + while (micros() - start < timeout_us_ && !echo_isr_.digital_read()) + ; + const uint32_t pulse_start = micros(); + while (micros() - start < timeout_us_ && echo_isr_.digital_read()) + ; + const uint32_t pulse_end = micros(); - ESP_LOGV(TAG, "Echo took %uµs", time); + ESP_LOGV(TAG, "Echo took %uµs", pulse_end - pulse_start); - if (time == 0) { + if (pulse_end - start >= timeout_us_) { ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str()); this->publish_state(NAN); } else { - float result = UltrasonicSensorComponent::us_to_m(time); + float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start); ESP_LOGD(TAG, "'%s' - Got distance: %.2f m", this->name_.c_str(), result); this->publish_state(result); } diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index 633c1b17fb..e0d71b99ef 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/gpio.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -10,7 +10,7 @@ namespace ultrasonic { class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent { public: void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; } - void set_echo_pin(GPIOPin *echo_pin) { echo_pin_ = echo_pin; } + void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; } /// Set the timeout for waiting for the echo in µs. void set_timeout_us(uint32_t timeout_us); @@ -34,7 +34,8 @@ class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent /// Helper function to convert the specified distance in meters to the echo duration in µs. GPIOPin *trigger_pin_; - GPIOPin *echo_pin_; + InternalGPIOPin *echo_pin_; + ISRInternalGPIOPin echo_isr_; uint32_t timeout_us_{}; /// 2 meters. uint32_t pulse_time_us_{}; }; diff --git a/esphome/components/uptime/uptime_sensor.cpp b/esphome/components/uptime/uptime_sensor.cpp index 755795ad53..40325d2a36 100644 --- a/esphome/components/uptime/uptime_sensor.cpp +++ b/esphome/components/uptime/uptime_sensor.cpp @@ -1,6 +1,7 @@ #include "uptime_sensor.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace uptime { diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index 65a4ec72bb..d68d69b79c 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -36,8 +36,6 @@ void VL53L0XSensor::setup() { if (!esphome::vl53l0x::VL53L0XSensor::enable_pin_setup_complete) { for (auto &vl53_sensor : vl53_sensors) { if (vl53_sensor->enable_pin_ != nullptr) { - // Disable the enable pin to force vl53 to HW Standby mode - ESP_LOGD(TAG, "i2c vl53l0x disable enable pins: GPIO%u", (vl53_sensor->enable_pin_)->get_pin()); // Set enable pin as OUTPUT and disable the enable pin to force vl53 to HW Standby mode vl53_sensor->enable_pin_->setup(); vl53_sensor->enable_pin_->digital_write(false); @@ -111,7 +109,7 @@ void VL53L0XSensor::setup() { reg(0xFF) = 0x00; reg(0x80) = 0x00; - uint8_t ref_spad_map[6]; + uint8_t ref_spad_map[6] = {}; this->read_bytes(0xB0, ref_spad_map, 6); reg(0xFF) = 0x01; @@ -294,7 +292,7 @@ void VL53L0XSensor::loop() { } if (this->waiting_for_interrupt_) { if (reg(0x13).get() & 0x07) { - uint16_t range_mm; + uint16_t range_mm = 0; this->read_byte_16(0x14 + 10, &range_mm); reg(0x0B) = 0x01; this->waiting_for_interrupt_ = false; diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index 83a6322dcd..a2e24e7550 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -3,7 +3,7 @@ #include #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f82a8893eb..97777a8986 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,8 +1,11 @@ +#ifdef USE_ARDUINO + #include "web_server.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/util.h" #include "esphome/components/json/json_util.h" +#include "esphome/components/network/util.h" #include "StreamString.h" @@ -154,7 +157,7 @@ void WebServer::setup() { } void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port()); if (this->using_auth()) { ESP_LOGCONFIG(TAG, " Basic authentication enabled"); } @@ -853,3 +856,5 @@ bool WebServer::isRequestHandlerTrivial() { return false; } } // namespace web_server } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 54d7356ac9..0eaa2e9a75 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/components/web_server_base/web_server_base.h" @@ -192,3 +194,5 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { } // namespace web_server } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 6351941aee..b0babcab46 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "web_server_base.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif @@ -27,12 +29,12 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin if (index == 0) { ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); this->ota_read_length_ = 0; -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 Update.runAsync(true); // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 if (Update.isRunning()) Update.abort(); success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); @@ -97,3 +99,5 @@ float WebServerBase::get_setup_priority() const { } // namespace web_server_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 158006ae40..4ef67f959c 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include #include "esphome/core/component.h" @@ -74,3 +76,5 @@ class OTARequestHandler : public AsyncWebHandler { } // namespace web_server_base } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c2943d0645..7d029bbea1 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition -from esphome.components.network import add_mdns_library from esphome.const import ( CONF_AP, CONF_BSSID, @@ -24,7 +23,6 @@ from esphome.const import ( CONF_STATIC_IP, CONF_SUBNET, CONF_USE_ADDRESS, - CONF_ENABLE_MDNS, CONF_PRIORITY, CONF_IDENTITY, CONF_CERTIFICATE_AUTHORITY, @@ -34,6 +32,7 @@ from esphome.const import ( CONF_EAP, ) from esphome.core import CORE, HexInt, coroutine_with_priority +from esphome.components.network import IPAddress from . import wpa2_eap @@ -41,7 +40,6 @@ AUTO_LOAD = ["network"] wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") -IPAddress = cg.global_ns.class_("IPAddress") ManualIP = wifi_ns.struct("ManualIP") WiFiComponent = wifi_ns.class_("WiFiComponent", cg.Component) WiFiAP = wifi_ns.struct("WiFiAP") @@ -155,18 +153,17 @@ def final_validate_power_esp32_ble(value): # WiFi should be in modem sleep (!=NONE) with BLE coexistence # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep return - framework_version = fv.get_arduino_framework_version() - if framework_version not in (None, "dev") and framework_version < "1.0.5": - # Only frameworks 1.0.5+ impacted - return - full = fv.full_config.get() for conflicting in [ "esp32_ble", "esp32_ble_beacon", "esp32_ble_server", "esp32_ble_tracker", ]: - if conflicting in full: + try: + cv.require_framework_version(esp32_arduino=cv.Version(1, 0, 5))(None) + except cv.Invalid: + pass + else: raise cv.Invalid( f"power_save_mode NONE is incompatible with {conflicting}. " f"Please remove the power save mode. See also " @@ -236,7 +233,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, cv.Optional(CONF_AP): WIFI_NETWORK_AP, - cv.Optional(CONF_ENABLE_MDNS, default=True): cv.boolean, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" @@ -249,6 +245,10 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( cv.decibel, cv.float_range(min=10.0, max=20.5) ), + cv.Optional("enable_mdns"): cv.invalid( + "This option has been removed. Please use the [disabled] option under the " + "new mdns component instead." + ), } ), _validate, @@ -345,9 +345,6 @@ async def to_code(config): cg.add_define("USE_WIFI") - if config[CONF_ENABLE_MDNS]: - add_mdns_library() - # Register at end for OTA safe mode await cg.register_component(var, config) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0a1e347e8f..6e0ce8c044 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1,9 +1,9 @@ #include "wifi_component.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32) || defined(USE_ESP_IDF) #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #endif @@ -14,7 +14,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" #include "esphome/core/application.h" @@ -39,7 +39,7 @@ void WiFiComponent::setup() { this->wifi_pre_setup_(); uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences.make_preference(hash, true); + this->pref_ = global_preferences->make_preference(hash, true); SavedWifiSettings save{}; if (this->pref_.load(&save)) { @@ -83,12 +83,10 @@ void WiFiComponent::setup() { esp32_improv::global_improv_component->start(); #endif this->wifi_apply_hostname_(); -#if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS) - network_setup_mdns(); -#endif } void WiFiComponent::loop() { + this->wifi_loop_(); const uint32_t now = millis(); if (this->has_sta()) { @@ -158,8 +156,6 @@ void WiFiComponent::loop() { } } } - - network_tick_mdns(); } WiFiComponent::WiFiComponent() { global_wifi_component = this; } @@ -167,9 +163,9 @@ WiFiComponent::WiFiComponent() { global_wifi_component = this; } bool WiFiComponent::has_ap() const { return this->has_ap_; } bool WiFiComponent::has_sta() const { return !this->sta_.empty(); } void WiFiComponent::set_fast_connect(bool fast_connect) { this->fast_connect_ = fast_connect; } -IPAddress WiFiComponent::get_ip_address() { +network::IPAddress WiFiComponent::get_ip_address() { if (this->has_sta()) - return this->wifi_sta_ip_(); + return this->wifi_sta_ip(); if (this->has_ap()) return this->wifi_soft_ap_ip(); return {}; @@ -205,16 +201,13 @@ void WiFiComponent::setup_ap_config_() { ESP_LOGCONFIG(TAG, " AP Password: '%s'", this->ap_.get_password().c_str()); if (this->ap_.get_manual_ip().has_value()) { auto manual = *this->ap_.get_manual_ip(); - ESP_LOGCONFIG(TAG, " AP Static IP: '%s'", manual.static_ip.toString().c_str()); - ESP_LOGCONFIG(TAG, " AP Gateway: '%s'", manual.gateway.toString().c_str()); - ESP_LOGCONFIG(TAG, " AP Subnet: '%s'", manual.subnet.toString().c_str()); + ESP_LOGCONFIG(TAG, " AP Static IP: '%s'", manual.static_ip.str().c_str()); + ESP_LOGCONFIG(TAG, " AP Gateway: '%s'", manual.gateway.str().c_str()); + ESP_LOGCONFIG(TAG, " AP Subnet: '%s'", manual.subnet.str().c_str()); } this->ap_setup_ = this->wifi_start_ap_(this->ap_); - ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip().toString().c_str()); -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - network_setup_mdns(this->wifi_soft_ap_ip(), 1); -#endif + ESP_LOGCONFIG(TAG, " IP Address: %s", this->wifi_soft_ap_ip().str().c_str()); if (!this->has_sta()) { this->state_ = WIFI_COMPONENT_STATE_AP; @@ -286,9 +279,8 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) { } if (ap.get_manual_ip().has_value()) { ManualIP m = *ap.get_manual_ip(); - ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.toString().c_str(), - m.gateway.toString().c_str(), m.subnet.toString().c_str(), m.dns1.toString().c_str(), - m.dns2.toString().c_str()); + ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(), + m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str()); } else { ESP_LOGV(TAG, " Using DHCP IP"); } @@ -353,26 +345,23 @@ const LogString *get_signal_bars(int8_t rssi) { } void WiFiComponent::print_connect_params_() { - uint8_t bssid[6] = {}; - uint8_t *raw_bssid = WiFi.BSSID(); - if (raw_bssid != nullptr) - memcpy(bssid, raw_bssid, sizeof(bssid)); + bssid_t bssid = wifi_bssid(); - ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), WiFi.SSID().c_str()); - ESP_LOGCONFIG(TAG, " IP Address: %s", WiFi.localIP().toString().c_str()); + ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'"), wifi_ssid().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", wifi_sta_ip().str().c_str()); ESP_LOGCONFIG(TAG, " BSSID: " LOG_SECRET("%02X:%02X:%02X:%02X:%02X:%02X"), bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); - int8_t rssi = WiFi.RSSI(); + int8_t rssi = wifi_rssi(); ESP_LOGCONFIG(TAG, " Signal strength: %d dB %s", rssi, LOG_STR_ARG(get_signal_bars(rssi))); if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } - ESP_LOGCONFIG(TAG, " Channel: %d", WiFi.channel()); - ESP_LOGCONFIG(TAG, " Subnet: %s", WiFi.subnetMask().toString().c_str()); - ESP_LOGCONFIG(TAG, " Gateway: %s", WiFi.gatewayIP().toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS1: %s", WiFi.dnsIP(0).toString().c_str()); - ESP_LOGCONFIG(TAG, " DNS2: %s", WiFi.dnsIP(1).toString().c_str()); + ESP_LOGCONFIG(TAG, " Channel: %d", wifi_channel_()); + ESP_LOGCONFIG(TAG, " Subnet: %s", wifi_subnet_mask_().str().c_str()); + ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); + ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); + ESP_LOGCONFIG(TAG, " DNS2: %s", wifi_dns_ip_(1).str().c_str()); } void WiFiComponent::start_scanning() { @@ -500,10 +489,10 @@ void WiFiComponent::dump_config() { } void WiFiComponent::check_connecting_finished() { - wl_status_t status = this->wifi_sta_status_(); + auto status = this->wifi_sta_connect_status_(); - if (status == WL_CONNECTED) { - if (WiFi.SSID().equals("")) { + if (status == WiFiSTAConnectStatus::CONNECTED) { + if (wifi_ssid().empty()) { ESP_LOGW(TAG, "Incomplete connection."); this->retry_connect(); return; @@ -527,9 +516,6 @@ void WiFiComponent::check_connecting_finished() { } #endif -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - network_setup_mdns(this->wifi_sta_ip_(), 0); -#endif this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; return; @@ -548,26 +534,23 @@ void WiFiComponent::check_connecting_finished() { return; } - if (status == WL_IDLE_STATUS || status == WL_DISCONNECTED || status == WL_CONNECTION_LOST) { - // WL_DISCONNECTED is set while not connected yet. - // WL_IDLE_STATUS is set while we're waiting for the IP address. - // WL_CONNECTION_LOST happens on the ESP32 + if (status == WiFiSTAConnectStatus::CONNECTING) { return; } - if (status == WL_NO_SSID_AVAIL) { + if (status == WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND) { ESP_LOGW(TAG, "WiFi network can not be found anymore."); this->retry_connect(); return; } - if (status == WL_CONNECT_FAILED) { + if (status == WiFiSTAConnectStatus::ERROR_CONNECT_FAILED) { ESP_LOGW(TAG, "Connecting to WiFi network failed. Are the credentials wrong?"); this->retry_connect(); return; } - ESP_LOGW(TAG, "WiFi Unknown connection status %d", status); + ESP_LOGW(TAG, "WiFi Unknown connection status %d", (int) status); } void WiFiComponent::retry_connect() { @@ -608,8 +591,8 @@ bool WiFiComponent::can_proceed() { } void WiFiComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } bool WiFiComponent::is_connected() { - return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && this->wifi_sta_status_() == WL_CONNECTED && - !this->error_from_callback_; + return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED && + this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_; } void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; } @@ -641,7 +624,7 @@ void WiFiAP::set_password(const std::string &password) { this->password_ = passw void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } -void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = std::move(manual_ip); } +void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } const optional &WiFiAP::get_bssid() const { return this->bssid_; } diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 3a4213c93c..d04f15695d 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -5,20 +5,20 @@ #include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include "esphome/components/network/ip_address.h" #include -#include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #include #include #endif -#ifdef ARDUINO_ARCH_ESP8266 -#include +#ifdef USE_ESP8266 #include +#include -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; @@ -54,13 +54,21 @@ enum WiFiComponentState { WIFI_COMPONENT_STATE_AP, }; +enum class WiFiSTAConnectStatus : int { + IDLE, + CONNECTING, + CONNECTED, + ERROR_NETWORK_NOT_FOUND, + ERROR_CONNECT_FAILED, +}; + /// Struct for setting static IPs in WiFiComponent. struct ManualIP { - IPAddress static_ip; - IPAddress gateway; - IPAddress subnet; - IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. - IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. + network::IPAddress static_ip; + network::IPAddress gateway; + network::IPAddress subnet; + network::IPAddress dns1; ///< The first DNS server. 0.0.0.0 for default. + network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; #ifdef USE_WIFI_WPA2_EAP @@ -153,6 +161,10 @@ enum WiFiPowerSaveMode { WIFI_POWER_SAVE_HIGH, }; +#ifdef USE_ESP_IDF +struct IDFWiFiEvent; +#endif + /// This component is responsible for managing the ESP WiFi interface. class WiFiComponent : public Component { public: @@ -207,13 +219,13 @@ class WiFiComponent : public Component { bool has_sta() const; bool has_ap() const; - IPAddress get_ip_address(); + network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); const std::vector &get_scan_result() const { return scan_result_; } - IPAddress wifi_soft_ap_ip(); + network::IPAddress wifi_soft_ap_ip(); bool has_sta_priority(const bssid_t &bssid) { for (auto &it : this->sta_priorities_) @@ -239,36 +251,46 @@ class WiFiComponent : public Component { }); } + network::IPAddress wifi_sta_ip(); + std::string wifi_ssid(); + bssid_t wifi_bssid(); + + int8_t wifi_rssi(); + protected: static std::string format_mac_addr(const uint8_t mac[6]); void setup_ap_config_(); void print_connect_params_(); + void wifi_loop_(); bool wifi_mode_(optional sta, optional ap); bool wifi_sta_pre_setup_(); bool wifi_apply_output_power_(float output_power); bool wifi_apply_power_save_(); bool wifi_sta_ip_config_(optional manual_ip); - IPAddress wifi_sta_ip_(); bool wifi_apply_hostname_(); bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); - wl_status_t wifi_sta_status_(); + WiFiSTAConnectStatus wifi_sta_connect_status_(); bool wifi_scan_start_(); bool wifi_ap_ip_config_(optional manual_ip); bool wifi_start_ap_(const WiFiAP &ap); bool wifi_disconnect_(); + int32_t wifi_channel_(); + network::IPAddress wifi_subnet_mask_(); + network::IPAddress wifi_gateway_ip_(); + network::IPAddress wifi_dns_ip_(int num); bool is_captive_portal_active_(); bool is_esp32_improv_active_(); -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); static void s_wifi_scan_done_callback(void *arg, STATUS status); #endif -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #if ESP_IDF_VERSION_MAJOR >= 4 void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); #else @@ -276,6 +298,9 @@ class WiFiComponent : public Component { #endif void wifi_scan_done_callback_(); #endif +#ifdef USE_ESP_IDF + void wifi_process_event_(IDFWiFiEvent *); +#endif std::string use_address_; std::vector sta_; diff --git a/esphome/components/wifi/wifi_component_esp32.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp similarity index 89% rename from esphome/components/wifi/wifi_component_esp32.cpp rename to esphome/components/wifi/wifi_component_esp32_arduino.cpp index 38c9e12bec..df2692ea81 100644 --- a/esphome/components/wifi/wifi_component_esp32.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -1,6 +1,6 @@ #include "wifi_component.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include @@ -15,7 +15,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/application.h" #include "esphome/core/util.h" @@ -24,6 +24,12 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFiClass::getMode(); bool current_sta = current_mode & 0b01; @@ -139,12 +145,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } -IPAddress WiFiComponent::wifi_sta_ip_() { +network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) - return IPAddress(); + return {}; tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } bool WiFiComponent::wifi_apply_hostname_() { @@ -280,6 +286,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { return false; } + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; } const char *get_auth_mode_str(uint8_t mode) { @@ -429,6 +441,7 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + s_sta_connected = true; break; } case SYSTEM_EVENT_STA_DISCONNECTED: { @@ -442,10 +455,14 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i buf[it.ssid_len] = '\0'; if (it.reason == WIFI_REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + s_sta_connect_error = true; } + s_sta_connected = false; + s_sta_connecting = false; break; } case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { @@ -474,10 +491,12 @@ void WiFiComponent::wifi_event_callback_(system_event_id_t event, system_event_i auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip).c_str(), format_ip4_addr(it.gw).c_str()); + s_sta_got_ip = true; break; } case SYSTEM_EVENT_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); + s_sta_got_ip = false; break; } case SYSTEM_EVENT_AP_START: { @@ -559,7 +578,21 @@ void WiFiComponent::wifi_pre_setup_() { // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { return WiFiClass::status(); } +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + if (s_sta_connected && s_sta_got_ip) { + return WiFiSTAConnectStatus::CONNECTED; + } + if (s_sta_connect_error) { + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + } + if (s_sta_connect_not_found) { + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + if (s_sta_connecting) { + return WiFiSTAConnectStatus::CONNECTING; + } + return WiFiSTAConnectStatus::IDLE; +} bool WiFiComponent::wifi_scan_start_() { // enable STA if (!this->wifi_mode_(true, {})) @@ -610,9 +643,9 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); } else { - info.ip.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(IPAddress(255, 255, 255, 0)); + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); @@ -630,13 +663,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { dhcps_lease_t lease; lease.enable = true; - IPAddress start_address = info.ip.addr; + network::IPAddress start_address = info.ip.addr; start_address[3] += 99; lease.start_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { @@ -694,14 +727,31 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } -IPAddress WiFiComponent::wifi_soft_ap_ip() { +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); - return IPAddress(ip.ip.addr); + return {ip.ip.addr}; } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t *raw_bssid = WiFi.BSSID(); + if (raw_bssid != nullptr) { + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + } + return bssid; +} +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } +void WiFiComponent::wifi_loop_() {} + } // namespace wifi } // namespace esphome -#endif +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 5a0ba92b09..5dcfe7a108 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,7 +1,7 @@ #include "wifi_component.h" #include "esphome/core/macros.h" -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include @@ -30,7 +30,7 @@ extern "C" { #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/util.h" #include "esphome/core/application.h" @@ -39,6 +39,12 @@ namespace wifi { static const char *const TAG = "wifi_esp8266"; +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = wifi_get_opmode(); bool current_sta = current_mode & 0b01; @@ -177,7 +183,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return ret; } -IPAddress WiFiComponent::wifi_sta_ip_() { +network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) return {}; struct ip_info ip {}; @@ -319,6 +325,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } } + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; } @@ -453,6 +465,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel); + s_sta_connected = true; break; } case EVENT_STAMODE_DISCONNECTED: { @@ -462,10 +475,14 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { buf[it.ssid_len] = '\0'; if (it.reason == REASON_NO_AP_FOUND) { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + s_sta_connect_error = true; } + s_sta_connected = false; + s_sta_connecting = false; break; } case EVENT_STAMODE_AUTHMODE_CHANGE: { @@ -487,6 +504,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { auto it = event->event_info.got_ip; ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), format_ip_addr(it.mask).c_str()); + s_sta_got_ip = true; break; } case EVENT_STAMODE_DHCP_TIMEOUT: { @@ -563,21 +581,22 @@ void WiFiComponent::wifi_pre_setup_() { this->wifi_mode_(false, false); } -wl_status_t WiFiComponent::wifi_sta_status_() { +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { station_status_t status = wifi_station_get_connect_status(); switch (status) { case STATION_GOT_IP: - return WL_CONNECTED; + return WiFiSTAConnectStatus::CONNECTED; case STATION_NO_AP_FOUND: - return WL_NO_SSID_AVAIL; + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + ; case STATION_CONNECT_FAIL: case STATION_WRONG_PASSWORD: - return WL_CONNECT_FAILED; - case STATION_IDLE: - return WL_IDLE_STATUS; + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; case STATION_CONNECTING: + return WiFiSTAConnectStatus::CONNECTING; + case STATION_IDLE: default: - return WL_DISCONNECTED; + return WiFiSTAConnectStatus::IDLE; } } bool WiFiComponent::wifi_scan_start_() { @@ -656,9 +675,9 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); } else { - info.ip.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.gw.addr = static_cast(IPAddress(192, 168, 4, 1)); - info.netmask.addr = static_cast(IPAddress(255, 255, 255, 0)); + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } if (wifi_softap_dhcps_status() == DHCP_STARTED) { @@ -677,13 +696,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { #endif struct dhcps_lease lease {}; - IPAddress start_address = info.ip.addr; + network::IPAddress start_address = info.ip.addr; start_address[3] += 99; lease.start_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.toString().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); if (!wifi_softap_set_dhcps_lease(&lease)) { ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!"); return false; @@ -746,11 +765,27 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } -IPAddress WiFiComponent::wifi_soft_ap_ip() { +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { struct ip_info ip {}; wifi_get_ip_info(SOFTAP_IF, &ip); return {ip.ip.addr}; } +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t *raw_bssid = WiFi.BSSID(); + if (raw_bssid != nullptr) { + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + } + return bssid; +} +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } +void WiFiComponent::wifi_loop_() {} } // namespace wifi } // namespace esphome diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp new file mode 100644 index 0000000000..676b7f8fba --- /dev/null +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -0,0 +1,901 @@ +#include "wifi_component.h" + +#ifdef USE_ESP_IDF + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef USE_WIFI_WPA2_EAP +#include +#endif +#include "lwip/err.h" +#include "lwip/dns.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace wifi { + +static const char *const TAG = "wifi_esp32"; + +static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static xQueueHandle s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_got_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_ap_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_not_found = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connect_error = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_wifi_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +struct IDFWiFiEvent { + esp_event_base_t event_base; + int32_t event_id; + union { + wifi_event_sta_scan_done_t sta_scan_done; + wifi_event_sta_connected_t sta_connected; + wifi_event_sta_disconnected_t sta_disconnected; + wifi_event_sta_authmode_change_t sta_authmode_change; + wifi_event_ap_staconnected_t ap_staconnected; + wifi_event_ap_stadisconnected_t ap_stadisconnected; + wifi_event_ap_probe_req_rx_t ap_probe_req_rx; + wifi_event_bss_rssi_low_t bss_rssi_low; + ip_event_got_ip_t ip_got_ip; + ip_event_ap_staipassigned_t ip_ap_staipassigned; + } data; +}; + +// general design: event handler translates events and pushes them to a queue, +// events get processed in the main loop +void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { + IDFWiFiEvent event; + memset(&event, 0, sizeof(IDFWiFiEvent)); + event.event_base = event_base; + event.event_id = event_id; + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_STOP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { + memcpy(&event.data.sta_authmode_change, event_data, sizeof(wifi_event_sta_authmode_change_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + memcpy(&event.data.sta_connected, event_data, sizeof(wifi_event_sta_connected_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { + memcpy(&event.data.sta_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STOP) { + // no data + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_PROBEREQRECVED) { + memcpy(&event.data.ap_probe_req_rx, event_data, sizeof(wifi_event_ap_probe_req_rx_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) { + memcpy(&event.data.ap_staconnected, event_data, sizeof(wifi_event_ap_staconnected_t)); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) { + memcpy(&event.data.ap_stadisconnected, event_data, sizeof(wifi_event_ap_stadisconnected_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED) { + memcpy(&event.data.ip_ap_staipassigned, event_data, sizeof(ip_event_ap_staipassigned_t)); + } else { + // did not match any event, don't send anything + return; + } + + // copy to heap to keep queue object small + auto *to_send = new IDFWiFiEvent; // NOLINT(cppcoreguidelines-owning-memory) + memcpy(to_send, &event, sizeof(IDFWiFiEvent)); + // don't block, we may miss events but the core can handle that + if (xQueueSend(s_event_queue, &to_send, 0L) != pdPASS) { + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + } +} + +void WiFiComponent::wifi_pre_setup_() { + esp_err_t err = esp_netif_init(); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err)); + return; + } + s_wifi_event_group = xEventGroupCreate(); + if (s_wifi_event_group == nullptr) { + ESP_LOGE(TAG, "xEventGroupCreate failed"); + return; + } + // NOLINTNEXTLINE(bugprone-sizeof-expression) + s_event_queue = xQueueCreate(64, sizeof(IDFWiFiEvent *)); + if (s_event_queue == nullptr) { + ESP_LOGE(TAG, "xQueueCreate failed"); + return; + } + err = esp_event_loop_create_default(); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err)); + return; + } + esp_event_handler_instance_t instance_wifi_id, instance_ip_id; + err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_wifi_id); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err)); + return; + } + err = esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, nullptr, &instance_ip_id); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_event_handler_instance_register failed: %s", esp_err_to_name(err)); + return; + } + + s_sta_netif = esp_netif_create_default_wifi_sta(); + s_ap_netif = esp_netif_create_default_wifi_ap(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + // cfg.nvs_enable = false; + err = esp_wifi_init(&cfg); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err)); + return; + } + err = esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (err != ERR_OK) { + ESP_LOGE(TAG, "esp_wifi_set_storage failed: %s", esp_err_to_name(err)); + return; + } +} + +bool WiFiComponent::wifi_mode_(optional sta, optional ap) { + esp_err_t err; + wifi_mode_t current_mode = WIFI_MODE_NULL; + if (s_wifi_started) { + err = esp_wifi_get_mode(¤t_mode); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_get_mode failed: %s", esp_err_to_name(err)); + return false; + } + } + bool current_sta = current_mode == WIFI_MODE_STA || current_mode == WIFI_MODE_APSTA; + bool current_ap = current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA; + + bool set_sta = sta.has_value() ? *sta : current_sta; + bool set_ap = ap.has_value() ? *ap : current_ap; + + wifi_mode_t set_mode; + if (set_sta && set_ap) + set_mode = WIFI_MODE_APSTA; + else if (set_sta && !set_ap) + set_mode = WIFI_MODE_STA; + else if (!set_sta && set_ap) + set_mode = WIFI_MODE_AP; + else + set_mode = WIFI_MODE_NULL; + + if (current_mode == set_mode) + return true; + + if (set_sta && !current_sta) { + ESP_LOGV(TAG, "Enabling STA."); + } else if (!set_sta && current_sta) { + ESP_LOGV(TAG, "Disabling STA."); + } + if (set_ap && !current_ap) { + ESP_LOGV(TAG, "Enabling AP."); + } else if (!set_ap && current_ap) { + ESP_LOGV(TAG, "Disabling AP."); + } + + if (set_mode == WIFI_MODE_NULL && s_wifi_started) { + err = esp_wifi_stop(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_stop failed: %s", esp_err_to_name(err)); + return false; + } + s_wifi_started = false; + return true; + } + + err = esp_wifi_set_mode(set_mode); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err)); + return false; + } + + if (set_mode != WIFI_MODE_NULL && !s_wifi_started) { + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err)); + return false; + } + s_wifi_started = true; + } + + return true; +} + +bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); } + +bool WiFiComponent::wifi_apply_output_power_(float output_power) { + int8_t val = static_cast(output_power * 4); + return esp_wifi_set_max_tx_power(val) == ESP_OK; +} + +bool WiFiComponent::wifi_apply_power_save_() { + wifi_ps_type_t power_save; + switch (this->power_save_) { + case WIFI_POWER_SAVE_LIGHT: + power_save = WIFI_PS_MIN_MODEM; + break; + case WIFI_POWER_SAVE_HIGH: + power_save = WIFI_PS_MAX_MODEM; + break; + case WIFI_POWER_SAVE_NONE: + default: + power_save = WIFI_PS_NONE; + break; + } + return esp_wifi_set_ps(power_save) == ESP_OK; +} + +bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t + wifi_config_t conf; + memset(&conf, 0, sizeof(conf)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + + // The weakest authmode to accept in the fast scan mode + if (ap.get_password().empty()) { + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; + } else { + conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + +#ifdef USE_WIFI_WPA2_EAP + if (ap.get_eap().has_value()) { + conf.sta.threshold.authmode = WIFI_AUTH_WPA2_ENTERPRISE; + } +#endif + + if (ap.get_bssid().has_value()) { + conf.sta.bssid_set = true; + memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); + } else { + conf.sta.bssid_set = false; + } + if (ap.get_channel().has_value()) { + conf.sta.channel = *ap.get_channel(); + conf.sta.scan_method = WIFI_FAST_SCAN; + } else { + conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + } + // Listen interval for ESP32 station to receive beacon when WIFI_PS_MAX_MODEM is set. + // Units: AP beacon intervals. Defaults to 3 if set to 0. + conf.sta.listen_interval = 0; + +#if ESP_IDF_VERSION_MAJOR >= 4 + // Protected Management Frame + // Device will prefer to connect in PMF mode if other device also advertizes PMF capability. + conf.sta.pmf_cfg.capable = true; + conf.sta.pmf_cfg.required = false; +#endif + + // note, we do our own filtering + // The minimum rssi to accept in the fast scan mode + conf.sta.threshold.rssi = -127; + + conf.sta.threshold.authmode = WIFI_AUTH_OPEN; + + wifi_config_t current_conf; + esp_err_t err; + err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_wifi_get_config failed: %s", esp_err_to_name(err)); + // can continue + } + + if (memcmp(¤t_conf, &conf, sizeof(wifi_config_t)) != 0) { + err = esp_wifi_disconnect(); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_disconnect failed: %s", esp_err_to_name(err)); + return false; + } + } + + err = esp_wifi_set_config(WIFI_IF_STA, &conf); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_set_config failed: %s", esp_err_to_name(err)); + return false; + } + + if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { + return false; + } + + // setup enterprise authentication if required +#ifdef USE_WIFI_WPA2_EAP + if (ap.get_eap().has_value()) { + // note: all certificates and keys have to be null terminated. Lengths are appended by +1 to include \0. + EAPAuth eap = ap.get_eap().value(); + err = esp_wifi_sta_wpa2_ent_set_identity((uint8_t *) eap.identity.c_str(), eap.identity.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_identity failed! %d", err); + } + int ca_cert_len = strlen(eap.ca_cert); + int client_cert_len = strlen(eap.client_cert); + int client_key_len = strlen(eap.client_key); + if (ca_cert_len) { + err = esp_wifi_sta_wpa2_ent_set_ca_cert((uint8_t *) eap.ca_cert, ca_cert_len + 1); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_ca_cert failed! %d", err); + } + } + // workout what type of EAP this is + // validation is not required as the config tool has already validated it + if (client_cert_len && client_key_len) { + // if we have certs, this must be EAP-TLS + err = esp_wifi_sta_wpa2_ent_set_cert_key((uint8_t *) eap.client_cert, client_cert_len + 1, + (uint8_t *) eap.client_key, client_key_len + 1, + (uint8_t *) eap.password.c_str(), strlen(eap.password.c_str())); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_cert_key failed! %d", err); + } + } else { + // in the absence of certs, assume this is username/password based + err = esp_wifi_sta_wpa2_ent_set_username((uint8_t *) eap.username.c_str(), eap.username.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_username failed! %d", err); + } + err = esp_wifi_sta_wpa2_ent_set_password((uint8_t *) eap.password.c_str(), eap.password.length()); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err); + } + } + esp_wpa2_config_t wpa2_config = WPA2_CONFIG_INIT_DEFAULT(); + err = esp_wifi_sta_wpa2_ent_enable(&wpa2_config); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); + } + } +#endif // USE_WIFI_WPA2_EAP + + err = esp_wifi_connect(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_connect failed: %s", esp_err_to_name(err)); + return false; + } + + s_sta_connecting = true; + s_sta_connected = false; + s_sta_got_ip = false; + s_sta_connect_error = false; + s_sta_connect_not_found = false; + return true; +} + +bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + tcpip_adapter_dhcp_status_t dhcp_status; + esp_err_t err = tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcpc_get_status failed: %s", esp_err_to_name(err)); + return false; + } + + if (!manual_ip.has_value()) { + // Use DHCP client + if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { + err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + if (err != ESP_OK) { + ESP_LOGV(TAG, "Starting DHCP client failed! %d", err); + } + return err == ESP_OK; + } + return true; + } + + tcpip_adapter_ip_info_t info; + memset(&info, 0, sizeof(info)); + info.ip.addr = static_cast(manual_ip->static_ip); + info.gw.addr = static_cast(manual_ip->gateway); + info.netmask.addr = static_cast(manual_ip->subnet); + + err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed: %s", esp_err_to_name(err)); + return false; + } + + ip_addr_t dns; + dns.type = IPADDR_TYPE_V4; + if (uint32_t(manual_ip->dns1) != 0) { + dns.u_addr.ip4.addr = static_cast(manual_ip->dns1); + dns_setserver(0, &dns); + } + if (uint32_t(manual_ip->dns2) != 0) { + dns.u_addr.ip4.addr = static_cast(manual_ip->dns2); + dns_setserver(1, &dns); + } + + return true; +} + +network::IPAddress WiFiComponent::wifi_sta_ip() { + if (!this->has_sta()) + return {}; + tcpip_adapter_ip_info_t ip; + esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_get_ip_info failed: %s", esp_err_to_name(err)); + return false; + } + return {ip.ip.addr}; +} + +bool WiFiComponent::wifi_apply_hostname_() { + // setting is done in SYSTEM_EVENT_STA_START callback + return true; +} +const char *get_auth_mode_str(uint8_t mode) { + switch (mode) { + case WIFI_AUTH_OPEN: + return "OPEN"; + case WIFI_AUTH_WEP: + return "WEP"; + case WIFI_AUTH_WPA_PSK: + return "WPA PSK"; + case WIFI_AUTH_WPA2_PSK: + return "WPA2 PSK"; + case WIFI_AUTH_WPA_WPA2_PSK: + return "WPA/WPA2 PSK"; + case WIFI_AUTH_WPA2_ENTERPRISE: + return "WPA2 Enterprise"; + case WIFI_AUTH_WPA3_PSK: + return "WPA3 PSK"; + case WIFI_AUTH_WPA2_WPA3_PSK: + return "WPA2/WPA3 PSK"; + case WIFI_AUTH_WAPI_PSK: + return "WAPI PSK"; + default: + return "UNKNOWN"; + } +} + +std::string format_ip4_addr(const esp_ip4_addr_t &ip) { + char buf[20]; + snprintf(buf, sizeof(buf), "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), + uint8_t(ip.addr >> 24)); + return buf; +} +const char *get_disconnect_reason_str(uint8_t reason) { + switch (reason) { + case WIFI_REASON_AUTH_EXPIRE: + return "Auth Expired"; + case WIFI_REASON_AUTH_LEAVE: + return "Auth Leave"; + case WIFI_REASON_ASSOC_EXPIRE: + return "Association Expired"; + case WIFI_REASON_ASSOC_TOOMANY: + return "Too Many Associations"; + case WIFI_REASON_NOT_AUTHED: + return "Not Authenticated"; + case WIFI_REASON_NOT_ASSOCED: + return "Not Associated"; + case WIFI_REASON_ASSOC_LEAVE: + return "Association Leave"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "Association not Authenticated"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "Disassociate Power Cap Bad"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "Disassociate Supported Channel Bad"; + case WIFI_REASON_IE_INVALID: + return "IE Invalid"; + case WIFI_REASON_MIC_FAILURE: + return "Mic Failure"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "4-Way Handshake Timeout"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "Group Key Update Timeout"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "IE In 4-Way Handshake Differs"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "Group Cipher Invalid"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "Pairwise Cipher Invalid"; + case WIFI_REASON_AKMP_INVALID: + return "AKMP Invalid"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "Unsupported RSN IE version"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "Invalid RSN IE Cap"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "802.1x Authentication Failed"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "Cipher Suite Rejected"; + case WIFI_REASON_BEACON_TIMEOUT: + return "Beacon Timeout"; + case WIFI_REASON_NO_AP_FOUND: + return "AP Not Found"; + case WIFI_REASON_AUTH_FAIL: + return "Authentication Failed"; + case WIFI_REASON_ASSOC_FAIL: + return "Association Failed"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "Handshake Failed"; + case WIFI_REASON_CONNECTION_FAIL: + return "Connection Failed"; + case WIFI_REASON_UNSPECIFIED: + default: + return "Unspecified"; + } +} + +void WiFiComponent::wifi_loop_() { + while (true) { + IDFWiFiEvent *data; + if (xQueueReceive(s_event_queue, &data, 0L) != pdTRUE) { + // no event ready + break; + } + + // process event + wifi_process_event_(data); + + delete data; // NOLINT(cppcoreguidelines-owning-memory) + } +} +void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { + esp_err_t err; + if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) { + ESP_LOGV(TAG, "Event: WiFi STA start"); + // apply hostname + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); + if (err != ERR_OK) { + ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + } + + s_sta_started = true; + // re-apply power save mode + wifi_apply_power_save_(); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) { + ESP_LOGV(TAG, "Event: WiFi STA stop"); + s_sta_started = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) { + const auto &it = data->data.sta_authmode_change; + ESP_LOGV(TAG, "Event: Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), + get_auth_mode_str(it.new_mode)); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) { + const auto &it = data->data.sta_connected; + char buf[33]; + assert(it.ssid_len <= 32); + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + ESP_LOGV(TAG, "Event: Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, + format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + s_sta_connected = true; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { + const auto &it = data->data.sta_disconnected; + char buf[33]; + assert(it.ssid_len <= 32); + memcpy(buf, it.ssid, it.ssid_len); + buf[it.ssid_len] = '\0'; + if (it.reason == WIFI_REASON_NO_AP_FOUND) { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + s_sta_connect_not_found = true; + + } else { + ESP_LOGW(TAG, "Event: Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, + format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + s_sta_connect_error = true; + } + s_sta_connected = false; + s_sta_connecting = false; + error_from_callback_ = true; + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { + const auto &it = data->data.ip_got_ip; + ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), + format_ip4_addr(it.ip_info.gw).c_str()); + s_sta_got_ip = true; + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { + ESP_LOGV(TAG, "Event: Lost IP"); + s_sta_got_ip = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_SCAN_DONE) { + const auto &it = data->data.sta_scan_done; + ESP_LOGV(TAG, "Event: WiFi Scan Done status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); + + scan_result_.clear(); + this->scan_done_ = true; + if (it.status != 0) { + // scan error + return; + } + + uint16_t number = it.number; + std::vector records(number); + err = esp_wifi_scan_get_ap_records(&number, records.data()); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed: %s", esp_err_to_name(err)); + return; + } + records.resize(number); + + scan_result_.reserve(number); + for (int i = 0; i < number; i++) { + auto &record = records[i]; + bssid_t bssid; + std::copy(record.bssid, record.bssid + 6, bssid.begin()); + std::string ssid(reinterpret_cast(record.ssid)); + WiFiScanResult result(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid.empty()); + scan_result_.push_back(result); + } + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) { + ESP_LOGV(TAG, "Event: WiFi AP start"); + s_ap_started = true; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STOP) { + ESP_LOGV(TAG, "Event: WiFi AP stop"); + s_ap_started = false; + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { + const auto &it = data->data.ap_probe_req_rx; + ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { + const auto &it = data->data.ap_staconnected; + ESP_LOGV(TAG, "Event: AP client connected MAC=%s", format_mac_addr(it.mac).c_str()); + + } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { + const auto &it = data->data.ap_stadisconnected; + ESP_LOGV(TAG, "Event: AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str()); + + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { + const auto &it = data->data.ip_ap_staipassigned; + ESP_LOGV(TAG, "Event: AP client assigned IP %s", format_ip4_addr(it.ip).c_str()); + } +} + +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + if (s_sta_connected && s_sta_got_ip) { + return WiFiSTAConnectStatus::CONNECTED; + } + if (s_sta_connect_error) { + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + } + if (s_sta_connect_not_found) { + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + if (s_sta_connecting) { + return WiFiSTAConnectStatus::CONNECTING; + } + return WiFiSTAConnectStatus::IDLE; +} +bool WiFiComponent::wifi_scan_start_() { + // enable STA + if (!this->wifi_mode_(true, {})) + return false; + + wifi_scan_config_t config{}; + config.ssid = nullptr; + config.bssid = nullptr; + config.channel = 0; + config.show_hidden = true; + config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + config.scan_time.active.min = 100; + config.scan_time.active.max = 300; + + esp_err_t err = esp_wifi_scan_start(&config, false); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_scan_start failed: %s", esp_err_to_name(err)); + return false; + } + + scan_done_ = false; + return true; +} +bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { + esp_err_t err; + + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + tcpip_adapter_ip_info_t info; + memset(&info, 0, sizeof(info)); + if (manual_ip.has_value()) { + info.ip.addr = static_cast(manual_ip->static_ip); + info.gw.addr = static_cast(manual_ip->gateway); + info.netmask.addr = static_cast(manual_ip->subnet); + } else { + info.ip.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); + info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); + } + tcpip_adapter_dhcp_status_t dhcp_status; + tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); + err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_stop failed! %d", err); + return false; + } + + err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info); + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed! %d", err); + return false; + } + + dhcps_lease_t lease; + lease.enable = true; + network::IPAddress start_address = info.ip.addr; + start_address[3] += 99; + lease.start_ip.addr = static_cast(start_address); + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); + start_address[3] += 100; + lease.end_ip.addr = static_cast(start_address); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); + + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_option failed! %d", err); + return false; + } + + err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + + if (err != ESP_OK) { + ESP_LOGV(TAG, "tcpip_adapter_dhcps_start failed! %d", err); + return false; + } + + return true; +} +bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { + // enable AP + if (!this->wifi_mode_({}, true)) + return false; + + wifi_config_t conf; + memset(&conf, 0, sizeof(conf)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + conf.ap.channel = ap.get_channel().value_or(1); + conf.ap.ssid_hidden = ap.get_ssid().size(); + conf.ap.max_connection = 5; + conf.ap.beacon_interval = 100; + + if (ap.get_password().empty()) { + conf.ap.authmode = WIFI_AUTH_OPEN; + *conf.ap.password = 0; + } else { + conf.ap.authmode = WIFI_AUTH_WPA2_PSK; + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + } + +#if ESP_IDF_VERSION_MAJOR >= 4 + // pairwise cipher of SoftAP, group cipher will be derived using this. + conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; +#endif + + esp_err_t err = esp_wifi_set_config(WIFI_IF_AP, &conf); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_wifi_set_config failed! %d", err); + return false; + } + + if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) { + ESP_LOGV(TAG, "wifi_ap_ip_config_ failed!"); + return false; + } + + return true; +} +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + return {ip.ip.addr}; +} +bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } + +bssid_t WiFiComponent::wifi_bssid() { + wifi_ap_record_t info; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + bssid_t res{}; + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return res; + } + std::copy(info.bssid, info.bssid + 6, res.begin()); + return res; +} +std::string WiFiComponent::wifi_ssid() { + wifi_ap_record_t info{}; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return ""; + } + auto *ssid_s = reinterpret_cast(info.ssid); + size_t len = strnlen(ssid_s, sizeof(info.ssid)); + return {ssid_s, len}; +} +int8_t WiFiComponent::wifi_rssi() { + wifi_ap_record_t info; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err)); + return 0; + } + return info.rssi; +} +int32_t WiFiComponent::wifi_channel_() { + uint8_t primary; + wifi_second_chan_t second; + esp_err_t err = esp_wifi_get_channel(&primary, &second); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_wifi_get_channel failed: %s", esp_err_to_name(err)); + return 0; + } + return primary; +} +network::IPAddress WiFiComponent::wifi_subnet_mask_() { + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + return {}; + } + return {ip.netmask.addr}; +} +network::IPAddress WiFiComponent::wifi_gateway_ip_() { + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + return {}; + } + return {ip.gw.addr}; +} +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { + const ip_addr_t *dns_ip = dns_getserver(num); + return {dns_ip->u_addr.ip4.addr}; +} + +} // namespace wifi +} // namespace esphome + +#endif diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 6d2be08fa0..de1c7f71fc 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -10,10 +10,10 @@ namespace wifi_info { class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - IPAddress ip = WiFi.localIP(); + auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; - this->publish_state(ip.toString().c_str()); + this->publish_state(ip.str().c_str()); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -21,15 +21,15 @@ class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { void dump_config() override; protected: - IPAddress last_ip_; + network::IPAddress last_ip_; }; class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - String ssid = WiFi.SSID(); - if (this->last_ssid_ != ssid.c_str()) { - this->last_ssid_ = std::string(ssid.c_str()); + std::string ssid = wifi::global_wifi_component->wifi_ssid(); + if (this->last_ssid_ != ssid) { + this->last_ssid_ = ssid; this->publish_state(this->last_ssid_); } } @@ -44,9 +44,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { public: void loop() override { - uint8_t *bssid = WiFi.BSSID(); - if (memcmp(bssid, this->last_bssid_.data(), 6) != 0) { - std::copy(bssid, bssid + 6, this->last_bssid_.data()); + wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid(); + if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) { + std::copy(bssid.begin(), bssid.end(), last_bssid_.begin()); char buf[30]; sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); this->publish_state(buf); diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 8fe108a530..f797aaa590 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -10,7 +10,7 @@ namespace wifi_signal { class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { public: - void update() override { this->publish_state(WiFi.RSSI()); } + void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); } void dump_config() override; std::string unique_id() override { return get_mac_address() + "-wifisignal"; } diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index c9e23bb7eb..2795529203 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -7,7 +7,7 @@ from esphome.const import CONF_NAME, CONF_PORT wled_ns = cg.esphome_ns.namespace("wled") WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) -CONFIG_SCHEMA = cv.Schema({}) +CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino) @register_addressable_effect( diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 500b9bbad2..8c68bca6e3 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -1,12 +1,14 @@ +#ifdef USE_ARDUINO + #include "wled_light_effect.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 #include #include #endif @@ -246,3 +248,5 @@ bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint } // namespace wled } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index 2a7654ec27..f0021ca978 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ARDUINO + #include "esphome/core/component.h" #include "esphome/components/light/addressable_light_effect.h" @@ -39,3 +41,5 @@ class WLEDLightEffect : public light::AddressableLightEffect { } // namespace wled } // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 3e7e591f9a..884969f793 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 #include #include "mbedtls/ccm.h" diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 7681cbd89c..54ab9a144f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_ble { diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index cfbe986fff..97bbd6e6d6 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgd1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgd1 { diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h index b9e05f857c..d05cffc4d1 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgd1 { diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 8f42eaecaf..a97ca93206 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgdk2.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgdk2 { diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h index 70f2ae9e2e..8fd9946537 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgdk2 { diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index d0a91d23f0..e1f83e4ddd 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgg1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgg1 { diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index e1d812e929..966c05ac79 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgg1 { diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 75ed947c25..db63beea89 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -1,7 +1,7 @@ #include "xiaomi_cgpr1.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgpr1 { diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h index 0b7369c798..eff4b1c6fb 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_cgpr1 { diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index f626daad88..990346e01e 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -1,7 +1,7 @@ #include "xiaomi_gcls002.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_gcls002 { diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h index d800e2837d..08e1bd7e54 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_gcls002 { diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 3f2cbf963a..30990b121d 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -1,7 +1,7 @@ #include "xiaomi_hhccjcy01.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccjcy01 { diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h index bd9d742b2d..aa99cc004a 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccjcy01 { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index 92b7171004..3ae29088bb 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -1,7 +1,7 @@ #include "xiaomi_hhccpot002.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccpot002 { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h index 1add8e27b1..ce746b9ee0 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_hhccpot002 { diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index 646796525e..1efebc2849 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -1,7 +1,7 @@ #include "xiaomi_jqjcy01ym.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_jqjcy01ym { diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h index d750e1e97f..ca1ad0f27e 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_jqjcy01ym { diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index 1ecf0606a2..a6f27c58b9 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsd02.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd02 { diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h index ec00464cb5..641a02bd5a 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd02 { diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 3eb0b68318..547cc7c114 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsd03mmc.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd03mmc { diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h index c2828e3cd1..95710a1508 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsd03mmc { diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index f16fce296b..749ca83afb 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -1,7 +1,7 @@ #include "xiaomi_lywsdcgq.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsdcgq { diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h index 553b5965fd..cbc76f9dd3 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_lywsdcgq { diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index fdbb799ea6..0cad5c67b2 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mhoc401.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mhoc401 { diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h index e80916f855..4ab882b2af 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mhoc401 { diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 36b4c8cc00..62378de72c 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -1,7 +1,7 @@ #include "xiaomi_miscale.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale { diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index d9da4f9421..409fdeaf93 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale { diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp index 34c5a975c7..9ae95c5f97 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.cpp @@ -1,7 +1,7 @@ #include "xiaomi_miscale2.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale2 { diff --git a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h index ead522e1f2..9f7ebf6a1f 100644 --- a/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h +++ b/esphome/components/xiaomi_miscale2/xiaomi_miscale2.h @@ -4,7 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_miscale2 { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index df1cede068..16c0b42279 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mjyd02yla.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mjyd02yla { diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h index 973b19a372..34b1fe4af0 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mjyd02yla { diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index 53429c1806..1a8e72bd2c 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -1,7 +1,7 @@ #include "xiaomi_mue4094rt.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mue4094rt { diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h index 31f913ec94..904c575ae6 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.h @@ -5,7 +5,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_mue4094rt { diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index 7529bb7431..b57bf5cd05 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -1,7 +1,7 @@ #include "xiaomi_wx08zm.h" #include "esphome/core/log.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_wx08zm { diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h index f3eba0e159..297c7ab47d 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.h @@ -6,7 +6,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/xiaomi_ble/xiaomi_ble.h" -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 namespace esphome { namespace xiaomi_wx08zm { diff --git a/esphome/components/zyaura/sensor.py b/esphome/components/zyaura/sensor.py index 74f1f9ec61..28a708b866 100644 --- a/esphome/components/zyaura/sensor.py +++ b/esphome/components/zyaura/sensor.py @@ -26,12 +26,8 @@ ZyAuraSensor = zyaura_ns.class_("ZyAuraSensor", cg.PollingComponent) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ZyAuraSensor), - cv.Required(CONF_CLOCK_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), - cv.Required(CONF_DATA_PIN): cv.All( - pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt - ), + cv.Required(CONF_CLOCK_PIN): cv.All(pins.internal_gpio_input_pin_schema), + cv.Required(CONF_DATA_PIN): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_CO2): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, icon=ICON_MOLECULE_CO2, diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp index 989791f098..11643a5c23 100644 --- a/esphome/components/zyaura/zyaura.cpp +++ b/esphome/components/zyaura/zyaura.cpp @@ -6,7 +6,7 @@ namespace zyaura { static const char *const TAG = "zyaura"; -bool ICACHE_RAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { +bool IRAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { // check if a new message has started, based on time since previous bit if ((ms - this->prev_ms_) > ZA_MAX_MS) { this->num_bits_ = 0; @@ -37,24 +37,24 @@ bool ICACHE_RAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { return false; } -void ZaSensorStore::setup(GPIOPin *pin_clock, GPIOPin *pin_data) { +void ZaSensorStore::setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data) { pin_clock->setup(); pin_data->setup(); this->pin_clock_ = pin_clock->to_isr(); this->pin_data_ = pin_data->to_isr(); - pin_clock->attach_interrupt(ZaSensorStore::interrupt, this, FALLING); + pin_clock->attach_interrupt(ZaSensorStore::interrupt, this, gpio::INTERRUPT_FALLING_EDGE); } -void ICACHE_RAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { +void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { uint32_t now = millis(); - bool data_bit = arg->pin_data_->digital_read(); + bool data_bit = arg->pin_data_.digital_read(); if (arg->processor_.decode(now, data_bit)) { arg->set_data_(arg->processor_.message); } } -void ICACHE_RAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { +void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { switch (message->type) { case HUMIDITY: this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f); @@ -82,7 +82,7 @@ bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) { sensor->publish_state(*value); // Sensor reported wrong value - if (isnan(*value)) { + if (std::isnan(*value)) { ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?"); this->status_set_warning(); return false; diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h index eed0c55c35..2b9e3fbb35 100644 --- a/esphome/components/zyaura/zyaura.h +++ b/esphome/components/zyaura/zyaura.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" namespace esphome { @@ -46,12 +46,12 @@ class ZaSensorStore { float temperature = NAN; float humidity = NAN; - void setup(GPIOPin *pin_clock, GPIOPin *pin_data); + void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data); static void interrupt(ZaSensorStore *arg); protected: - ISRInternalGPIOPin *pin_clock_; - ISRInternalGPIOPin *pin_data_; + ISRInternalGPIOPin pin_clock_; + ISRInternalGPIOPin pin_data_; ZaDataProcessor processor_; void set_data_(ZaMessage *message); @@ -60,8 +60,8 @@ class ZaSensorStore { /// Component for reading temperature/co2/humidity measurements from ZyAura sensors. class ZyAuraSensor : public PollingComponent { public: - void set_pin_clock(GPIOPin *pin) { pin_clock_ = pin; } - void set_pin_data(GPIOPin *pin) { pin_data_ = pin; } + void set_pin_clock(InternalGPIOPin *pin) { pin_clock_ = pin; } + void set_pin_data(InternalGPIOPin *pin) { pin_data_ = pin; } void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -73,8 +73,8 @@ class ZyAuraSensor : public PollingComponent { protected: ZaSensorStore store_; - GPIOPin *pin_clock_; - GPIOPin *pin_data_; + InternalGPIOPin *pin_clock_; + InternalGPIOPin *pin_data_; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/config.py b/esphome/config.py index e49293e6b2..af6c5b0b64 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1,4 +1,6 @@ -import collections +import abc +import functools +import heapq import logging import re @@ -15,6 +17,7 @@ from esphome.const import ( CONF_PACKAGES, CONF_SUBSTITUTIONS, CONF_EXTERNAL_COMPONENTS, + TARGET_PLATFORMS, ) from esphome.core import CORE, EsphomeError from esphome.helpers import indent @@ -56,6 +59,27 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other +@functools.total_ordering +class _ValidationStepTask: + def __init__(self, priority: float, id_number: int, step: "ConfigValidationStep"): + self.priority = priority + self.id_number = id_number + self.step = step + + @property + def _cmp_tuple(self) -> Tuple[float, int]: + return (-self.priority, self.id_number) + + def __eq__(self, other): + return self._cmp_tuple == other._cmp_tuple + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return self._cmp_tuple < other._cmp_tuple + + class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() @@ -68,6 +92,10 @@ class Config(OrderedDict, fv.FinalValidateConfig): # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] self._data = {} + # Store pending validation tasks (in heap order) + self._validation_tasks: List[_ValidationStepTask] = [] + # ID to ensure stable order for keys with equal priority + self._validation_tasks_id = 0 def add_error(self, error): # type: (vol.Invalid) -> None @@ -83,6 +111,18 @@ class Config(OrderedDict, fv.FinalValidateConfig): error.path = error.path[last_root + 1 :] self.errors.append(error) + def add_validation_step(self, step: "ConfigValidationStep"): + id_num = self._validation_tasks_id + self._validation_tasks_id += 1 + heapq.heappush( + self._validation_tasks, _ValidationStepTask(step.priority, id_num, step) + ) + + def run_validation_steps(self): + while self._validation_tasks: + task = heapq.heappop(self._validation_tasks) + task.step.run(self) + @contextmanager def catch_error(self, path=None): path = path or [] @@ -206,92 +246,6 @@ def iter_ids(config, path=None): yield from iter_ids(value, path + [key]) -def do_id_pass(result): # type: (Config) -> None - from esphome.cpp_generator import MockObjClass - from esphome.cpp_types import Component - - searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] - for id, path in iter_ids(result): - if id.is_declaration: - if id.id is not None: - # Look for duplicate definitions - match = next((v for v in result.declare_ids if v[0].id == id.id), None) - if match is not None: - opath = "->".join(str(v) for v in match[1]) - result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) - continue - result.declare_ids.append((id, path)) - else: - searching_ids.append((id, path)) - # Resolve default ids after manual IDs - for id, _ in result.declare_ids: - id.resolve([v[0].id for v in result.declare_ids]) - if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): - CORE.component_ids.add(id.id) - - # Check searched IDs - for id, path in searching_ids: - if id.id is not None: - # manually declared - match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) - if match is None or not match.is_manual: - # No declared ID with this name - import difflib - - error = f"Couldn't find ID '{id.id}'. Please check you have defined an ID with that name in your configuration." - # Find candidates - matches = difflib.get_close_matches( - id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] - ) - if matches: - matches_s = ", ".join(f'"{x}"' for x in matches) - error += f" These IDs look similar: {matches_s}." - result.add_str_error(error, path) - continue - if not isinstance(match.type, MockObjClass) or not isinstance( - id.type, MockObjClass - ): - continue - if not match.type.inherits_from(id.type): - result.add_str_error( - f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. Please double check your ID is pointing to the correct value", - path, - ) - - if id.id is None and id.type is not None: - matches = [] - for v in result.declare_ids: - if v[0] is None or not isinstance(v[0].type, MockObjClass): - continue - inherits = v[0].type.inherits_from(id.type) - if inherits: - matches.append(v[0]) - - if len(matches) == 0: - result.add_str_error( - f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", - path, - ) - elif len(matches) == 1: - id.id = matches[0].id - elif len(matches) > 1: - if str(id.type) == "time::RealTimeClock": - id.id = matches[0].id - else: - manual_declared_count = sum(1 for m in matches if m.is_manual) - if manual_declared_count > 0: - ids = ", ".join([f"'{m.id}'" for m in matches if m.is_manual]) - result.add_str_error( - f"Too many candidates found for '{path[-1]}' type '{id.type}' {'Some are' if manual_declared_count > 1 else 'One is'} {ids}", - path, - ) - else: - result.add_str_error( - f"Too many candidates found for '{path[-1]}' type '{id.type}' You must assign an explicit ID to the parent component you want to use.", - path, - ) - - def recursive_check_replaceme(value): if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) @@ -310,6 +264,389 @@ def recursive_check_replaceme(value): return value +class ConfigValidationStep(abc.ABC): + """A step to for the validation phase.""" + + # Priority of this step, higher means run earlier + priority: float = 0.0 + + @abc.abstractmethod + def run(self, result: Config) -> None: + ... + + +class LoadValidationStep(ConfigValidationStep): + """Load step, this step is called once for each domain config fragment. + + Responsibilties: + - Load component code + - Ensure all AUTO_LOADs are added + - Set output paths of result + """ + + def __init__(self, domain: str, conf: ConfigType): + self.domain = domain + self.conf = conf + + def run(self, result: Config) -> None: + if self.domain.startswith("."): + # Ignore top-level keys starting with a dot + return + result.add_output_path([self.domain], self.domain) + result[self.domain] = self.conf + component = get_component(self.domain) + path = [self.domain] + if component is None: + result.add_str_error(f"Component not found: {self.domain}", path) + return + CORE.loaded_integrations.add(self.domain) + + # Process AUTO_LOAD + for load in component.auto_load: + if load not in result: + result.add_validation_step(AutoLoadValidationStep(load)) + + if not component.is_platform_component: + result.add_validation_step( + MetadataValidationStep([self.domain], self.domain, self.conf, component) + ) + return + + # This is a platform component, proceed to reading platform entries + # Remove this is as an output path + result.remove_output_path([self.domain], self.domain) + + # Ensure conf is a list + if not self.conf: + result[self.domain] = self.conf = [] + elif not isinstance(self.conf, list): + result[self.domain] = self.conf = [self.conf] + + for i, p_config in enumerate(self.conf): + path = [self.domain, i] + # Construct temporary unknown output path + p_domain = f"{self.domain}.unknown" + result.add_output_path(path, p_domain) + result[self.domain][i] = p_config + if not isinstance(p_config, dict): + result.add_str_error("Platform schemas must be key-value pairs.", path) + continue + p_name = p_config.get("platform") + if p_name is None: + result.add_str_error("No platform specified! See 'platform' key.", path) + continue + # Remove temp output path and construct new one + result.remove_output_path(path, p_domain) + p_domain = f"{self.domain}.{p_name}" + result.add_output_path(path, p_domain) + # Try Load platform + platform = get_platform(self.domain, p_name) + if platform is None: + result.add_str_error(f"Platform not found: '{p_domain}'", path) + continue + CORE.loaded_integrations.add(p_name) + + # Process AUTO_LOAD + for load in platform.auto_load: + if load not in result: + result.add_validation_step(AutoLoadValidationStep(load)) + + result.add_validation_step( + MetadataValidationStep(path, p_domain, p_config, platform) + ) + + +class AutoLoadValidationStep(ConfigValidationStep): + """Auto load step. This step is used to automatically load components if + a component requested that with AUTO_LOAD. + """ + + # Only load after all regular loads have taken place + priority = -1.0 + + def __init__(self, domain: str): + self.domain = domain + + def run(self, result: Config) -> None: + if self.domain in result: + # already loaded + return + result.add_validation_step(LoadValidationStep(self.domain, core.AutoLoad())) + + +class MetadataValidationStep(ConfigValidationStep): + """Validate component metadata + + Responsibilties: + - Config transformation (nullable, multi conf) + - Check dependencies + - Check conflicts + - Check supported target platforms + """ + + # All components need to be loaded first to ensure dependency check works + priority = -2.0 + + def __init__( + self, + path: ConfigPath, + domain: str, + conf: ConfigType, + component: ComponentManifest, + ) -> None: + self.path = path + self.domain = domain + self.conf = conf + self.comp = component + + def run(self, result: Config) -> None: + if self.conf is None: + result[self.domain] = self.conf = {} + + success = True + for dependency in self.comp.dependencies: + if dependency not in result: + result.add_str_error( + f"Component {self.domain} requires component {dependency}", + self.path, + ) + success = False + if not success: + return + + success = True + for conflict in self.comp.conflicts_with: + if conflict in result: + result.add_str_error( + f"Component {self.domain} cannot be used together with component {conflict}", + self.path, + ) + success = False + if not success: + return + + if ( + not self.comp.is_platform_component + and self.comp.config_schema is None + and not isinstance(self.conf, core.AutoLoad) + ): + result.add_str_error( + f"Component {self.domain} cannot be loaded via YAML " + "(no CONFIG_SCHEMA).", + self.path, + ) + return + + if self.comp.multi_conf: + if not isinstance(self.conf, list): + result[self.domain] = self.conf = [self.conf] + if ( + not isinstance(self.comp.multi_conf, bool) + and len(self.conf) > self.comp.multi_conf + ): + result.add_str_error( + f"Component {self.domain} supports a maximum of {self.comp.multi_conf} " + f"entries ({len(self.conf)} found).", + self.path, + ) + return + for i, part_conf in enumerate(self.conf): + result.add_validation_step( + SchemaValidationStep( + self.domain, self.path + [i], part_conf, self.comp + ) + ) + return + + result.add_validation_step( + SchemaValidationStep(self.domain, self.path, self.conf, self.comp) + ) + + +class SchemaValidationStep(ConfigValidationStep): + """Schema validation step. + + During this step all CONFIG_SCHEMAs are checked against the configs. + """ + + def __init__( + self, domain: str, path: ConfigPath, conf: ConfigType, comp: ComponentManifest + ): + self.path = path + self.conf = conf + self.comp = comp + + def run(self, result: Config) -> None: + if self.comp.config_schema is None: + return + with result.catch_error(self.path): + if self.comp.is_platform: + # Remove 'platform' key for validation + input_conf = OrderedDict(self.conf) + platform_val = input_conf.pop("platform") + schema = cv.Schema(self.comp.config_schema) + validated = schema(input_conf) + # Ensure result is OrderedDict so we can call move_to_end + if not isinstance(validated, OrderedDict): + validated = OrderedDict(validated) + validated["platform"] = platform_val + validated.move_to_end("platform", last=False) + result.set_by_path(self.path, validated) + else: + schema = cv.Schema(self.comp.config_schema) + validated = schema(self.conf) + result.set_by_path(self.path, validated) + + result.add_validation_step(FinalValidateValidationStep(self.path, self.comp)) + + +class IDPassValidationStep(ConfigValidationStep): + """ID Pass step. + + During this step all ID references are checked. + + If an automatic ID reference is used, a fitting declared ID is automatically searched. + Also checks duplicate ID names, and that referenced IDs are declared. + """ + + # Has to happen after all schemas validated + priority = -10.0 + + def __init__(self) -> None: + pass + + def run(self, result: Config) -> None: + from esphome.cpp_generator import MockObjClass + from esphome.cpp_types import Component + + if result.errors: + # If result already has errors, skip this step + # Otherwise the user will get a bunch of missing ID warnings + # because the component that did not validate doesn't have any IDs set + return + + searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + for id, path in iter_ids(result): + if id.is_declaration: + if id.id is not None: + # Look for duplicate definitions + match = next( + (v for v in result.declare_ids if v[0].id == id.id), None + ) + if match is not None: + opath = "->".join(str(v) for v in match[1]) + result.add_str_error( + f"ID {id.id} redefined! Check {opath}", path + ) + continue + result.declare_ids.append((id, path)) + else: + searching_ids.append((id, path)) + + # Resolve default ids after manual IDs + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) + if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): + CORE.component_ids.add(id.id) + + # Check searched IDs + for id, path in searching_ids: + if id.id is not None: + # manually declared + match = next( + (v[0] for v in result.declare_ids if v[0].id == id.id), None + ) + if match is None or not match.is_manual: + # No declared ID with this name + import difflib + + error = ( + f"Couldn't find ID '{id.id}'. Please check you have defined " + "an ID with that name in your configuration." + ) + # Find candidates + matches = difflib.get_close_matches( + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] + ) + if matches: + matches_s = ", ".join(f'"{x}"' for x in matches) + error += f" These IDs look similar: {matches_s}." + result.add_str_error(error, path) + continue + if not isinstance(match.type, MockObjClass) or not isinstance( + id.type, MockObjClass + ): + continue + if not match.type.inherits_from(id.type): + result.add_str_error( + f"ID '{id.id}' of type {match.type} doesn't inherit from {id.type}. " + "Please double check your ID is pointing to the correct value", + path, + ) + + if id.id is None and id.type is not None: + matches = [] + for v in result.declare_ids: + if v[0] is None or not isinstance(v[0].type, MockObjClass): + continue + inherits = v[0].type.inherits_from(id.type) + if inherits: + matches.append(v[0]) + + if len(matches) == 0: + result.add_str_error( + f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", + path, + ) + elif len(matches) == 1: + id.id = matches[0].id + elif len(matches) > 1: + if str(id.type) == "time::RealTimeClock": + id.id = matches[0].id + else: + manual_declared_count = sum(1 for m in matches if m.is_manual) + if manual_declared_count > 0: + ids = ", ".join( + [f"'{m.id}'" for m in matches if m.is_manual] + ) + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' {'Some are' if manual_declared_count > 1 else 'One is'} {ids}", + path, + ) + else: + result.add_str_error( + f"Too many candidates found for '{path[-1]}' type '{id.type}' You must assign an explicit ID to the parent component you want to use.", + path, + ) + + +class FinalValidateValidationStep(ConfigValidationStep): + """Run final_validate_schema for all components.""" + + # Has to happen after ID pass validated + priority = -20.0 + + def __init__(self, path: ConfigPath, comp: ComponentManifest) -> None: + self.path = path + self.comp = comp + + def run(self, result: Config) -> None: + if result.errors: + # If result already has errors, skip this step + return + + if self.comp.final_validate_schema is None: + return + + token = fv.full_config.set(result) + + conf = result.get_nested_item(self.path) + with result.catch_error(self.path): + self.comp.final_validate_schema(conf) + + fv.full_config.reset(token) + + def validate_config(config, command_line_substitutions): result = Config() @@ -384,217 +721,31 @@ def validate_config(config, command_line_substitutions): result[CONF_ESPHOME] = config[CONF_ESPHOME] result.add_output_path([CONF_ESPHOME], CONF_ESPHOME) try: - core_config.preload_core_config(config) + core_config.preload_core_config(config, result) except vol.Invalid as err: result.add_error(err) return result # Remove temporary esphome config path again, it will be reloaded later result.remove_output_path([CONF_ESPHOME], CONF_ESPHOME) - # 3. Load components. - # Load components (also AUTO_LOAD) and set output paths of result - # Queue of items to load, FIFO - load_queue = collections.deque() + # First run platform validation steps + for key in TARGET_PLATFORMS: + if key in config: + result.add_validation_step(LoadValidationStep(key, config[key])) + result.run_validation_steps() + + if result.errors: + return result + for domain, conf in config.items(): - load_queue.append((domain, conf)) + result.add_validation_step(LoadValidationStep(domain, conf)) + result.add_validation_step(IDPassValidationStep()) - # List of items to enter next stage - check_queue = ( - [] - ) # type: List[Tuple[ConfigPath, str, ConfigType, ComponentManifest]] - - # This step handles: - # - Adding output path - # - Auto Load - # - Loading configs into result - - while load_queue: - domain, conf = load_queue.popleft() - if domain.startswith("."): - # Ignore top-level keys starting with a dot - continue - result.add_output_path([domain], domain) - result[domain] = conf - component = get_component(domain) - path = [domain] - if component is None: - result.add_str_error(f"Component not found: {domain}", path) - continue - CORE.loaded_integrations.add(domain) - - # Process AUTO_LOAD - for load in component.auto_load: - if load not in config: - load_conf = core.AutoLoad() - config[load] = load_conf - load_queue.append((load, load_conf)) - - if not component.is_platform_component: - check_queue.append(([domain], domain, conf, component)) - continue - - # This is a platform component, proceed to reading platform entries - # Remove this is as an output path - result.remove_output_path([domain], domain) - - # Ensure conf is a list - if not conf: - result[domain] = conf = [] - elif not isinstance(conf, list): - result[domain] = conf = [conf] - - for i, p_config in enumerate(conf): - path = [domain, i] - # Construct temporary unknown output path - p_domain = f"{domain}.unknown" - result.add_output_path(path, p_domain) - result[domain][i] = p_config - if not isinstance(p_config, dict): - result.add_str_error("Platform schemas must be key-value pairs.", path) - continue - p_name = p_config.get("platform") - if p_name is None: - result.add_str_error("No platform specified! See 'platform' key.", path) - continue - # Remove temp output path and construct new one - result.remove_output_path(path, p_domain) - p_domain = f"{domain}.{p_name}" - result.add_output_path(path, p_domain) - # Try Load platform - platform = get_platform(domain, p_name) - if platform is None: - result.add_str_error(f"Platform not found: '{p_domain}'", path) - continue - CORE.loaded_integrations.add(p_name) - - # Process AUTO_LOAD - for load in platform.auto_load: - if load not in config: - load_conf = core.AutoLoad() - config[load] = load_conf - load_queue.append((load, load_conf)) - - check_queue.append((path, p_domain, p_config, platform)) - - # 4. Validate component metadata, including - # - Transformation (nullable, multi conf) - # - Dependencies - # - Conflicts - # - Supported ESP Platform - - # List of items to proceed to next stage - validate_queue = [] # type: List[Tuple[ConfigPath, ConfigType, ComponentManifest]] - for path, domain, conf, comp in check_queue: - if conf is None: - result[domain] = conf = {} - - success = True - for dependency in comp.dependencies: - if dependency not in config: - result.add_str_error( - f"Component {domain} requires component {dependency}", - path, - ) - success = False - if not success: - continue - - success = True - for conflict in comp.conflicts_with: - if conflict in config: - result.add_str_error( - f"Component {domain} cannot be used together with component {conflict}", - path, - ) - success = False - if not success: - continue - - if CORE.esp_platform not in comp.esp_platforms: - result.add_str_error( - f"Component {domain} doesn't support {CORE.esp_platform}.", - path, - ) - continue - - if ( - not comp.is_platform_component - and comp.config_schema is None - and not isinstance(conf, core.AutoLoad) - ): - result.add_str_error( - f"Component {domain} cannot be loaded via YAML (no CONFIG_SCHEMA).", - path, - ) - continue - - if comp.multi_conf: - if not isinstance(conf, list): - result[domain] = conf = [conf] - if not isinstance(comp.multi_conf, bool) and len(conf) > comp.multi_conf: - result.add_str_error( - f"Component {domain} supports a maximum of {comp.multi_conf} entries ({len(conf)} found).", - path, - ) - continue - for i, part_conf in enumerate(conf): - validate_queue.append((path + [i], part_conf, comp)) - continue - - validate_queue.append((path, conf, comp)) - - # 5. Validate configuration schema - for path, conf, comp in validate_queue: - if comp.config_schema is None: - continue - with result.catch_error(path): - if comp.is_platform: - # Remove 'platform' key for validation - input_conf = OrderedDict(conf) - platform_val = input_conf.pop("platform") - validated = comp.config_schema(input_conf) - # Ensure result is OrderedDict so we can call move_to_end - if not isinstance(validated, OrderedDict): - validated = OrderedDict(validated) - validated["platform"] = platform_val - validated.move_to_end("platform", last=False) - result.set_by_path(path, validated) - else: - validated = comp.config_schema(conf) - result.set_by_path(path, validated) - - # 6. If no validation errors, check IDs - if not result.errors: - # Only parse IDs if no validation error. Otherwise - # user gets confusing messages - do_id_pass(result) - - # 7. Final validation - if not result.errors: - # Inter - components validation - token = fv.full_config.set(result) - - for path, _, comp in validate_queue: - if comp.final_validate_schema is None: - continue - conf = result.get_nested_item(path) - with result.catch_error(path): - comp.final_validate_schema(conf) - - fv.full_config.reset(token) + result.run_validation_steps() return result -def _nested_getitem(data, path): - for item_index in path: - try: - data = data[item_index] - except (KeyError, IndexError, TypeError): - return None - return data - - def humanize_error(config, validation_error): validation_error = str(validation_error) m = re.match( @@ -661,7 +812,6 @@ def _load_config(command_line_substitutions): config = yaml_util.load_yaml(CORE.config_path) except EsphomeError as e: raise InvalidYAMLError(e) from e - CORE.raw_config = config try: result = validate_config(config, command_line_substitutions) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a864c65dc7..0d8f4f3b64 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1,5 +1,6 @@ """Helpers for config validation using voluptuous.""" +from dataclasses import dataclass import logging import os import re @@ -33,6 +34,9 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, ) from esphome.core import ( CORE, @@ -492,20 +496,37 @@ def templatable(other_validators): def only_on(platforms): - """Validate that this option can only be specified on the given ESP platforms.""" + """Validate that this option can only be specified on the given target platforms.""" if not isinstance(platforms, list): platforms = [platforms] def validator_(obj): - if CORE.esp_platform not in platforms: + if CORE.target_platform not in platforms: raise Invalid(f"This feature is only available on {platforms}") return obj return validator_ -only_on_esp32 = only_on("ESP32") -only_on_esp8266 = only_on("ESP8266") +def only_with_framework(frameworks): + """Validate that this option can only be specified on the given frameworks.""" + if not isinstance(frameworks, list): + frameworks = [frameworks] + + def validator_(obj): + if CORE.target_framework not in frameworks: + raise Invalid( + f"This feature is only available with frameworks {frameworks}" + ) + return obj + + return validator_ + + +only_on_esp32 = only_on("esp32") +only_on_esp8266 = only_on("esp8266") +only_with_arduino = only_with_framework("arduino") +only_with_esp_idf = only_with_framework("esp-idf") # Adapted from: @@ -1025,7 +1046,7 @@ def requires_component(comp): # pylint: disable=unsupported-membership-test def validator(value): # pylint: disable=unsupported-membership-test - if comp not in CORE.raw_config: + if comp not in CORE.loaded_integrations: raise Invalid(f"This option requires component {comp}") return value @@ -1401,18 +1422,32 @@ class GenerateID(Optional): class SplitDefault(Optional): """Mark this key to have a split default for ESP8266/ESP32.""" - def __init__(self, key, esp8266=vol.UNDEFINED, esp32=vol.UNDEFINED): + def __init__( + self, + key, + esp8266=vol.UNDEFINED, + esp32=vol.UNDEFINED, + esp32_arduino=vol.UNDEFINED, + esp32_idf=vol.UNDEFINED, + ): super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) - self._esp32_default = vol.default_factory(esp32) + self._esp32_arduino_default = vol.default_factory( + esp32_arduino if esp32 is vol.UNDEFINED else esp32 + ) + self._esp32_idf_default = vol.default_factory( + esp32_idf if esp32 is vol.UNDEFINED else esp32 + ) @property def default(self): if CORE.is_esp8266: return self._esp8266_default - if CORE.is_esp32: - return self._esp32_default - raise ValueError + if CORE.is_esp32 and CORE.using_arduino: + return self._esp32_arduino_default + if CORE.is_esp32 and CORE.using_esp_idf: + return self._esp32_idf_default + raise NotImplementedError @default.setter def default(self, value): @@ -1431,7 +1466,7 @@ class OnlyWith(Optional): @property def default(self): # pylint: disable=unsupported-membership-test - if self._component in CORE.raw_config: + if self._component in CORE.loaded_integrations: return self._default return vol.UNDEFINED @@ -1613,3 +1648,73 @@ def source_refresh(value: str): if value.lower() == "never": return source_refresh("1000y") return positive_time_period_seconds(value) + + +@dataclass(frozen=True, order=True) +class Version: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + @classmethod + def parse(cls, value: str) -> "Version": + match = re.match(r"(\d+).(\d+).(\d+)", value) + if match is None: + raise ValueError(f"Not a valid version number {value}") + major = int(match[1]) + minor = int(match[2]) + patch = int(match[3]) + return Version(major=major, minor=minor, patch=patch) + + +def version_number(value): + value = string_strict(value) + try: + return str(Version.parse(value)) + except ValueError as e: + raise Invalid("Not a version number") from e + + +def require_framework_version( + *, + esp_idf=None, + esp32_arduino=None, + esp8266_arduino=None, +): + def validator(value): + core_data = CORE.data[KEY_CORE] + framework = core_data[KEY_TARGET_FRAMEWORK] + if framework == "esp-idf": + if esp_idf is None: + raise Invalid("This feature is incompatible with esp-idf") + required = esp_idf + elif CORE.is_esp32 and framework == "arduino": + if esp32_arduino is None: + raise Invalid( + "This feature is incompatible with ESP32 using arduino framework" + ) + required = esp32_arduino + elif CORE.is_esp8266 and framework == "arduino": + if esp8266_arduino is None: + raise Invalid("This feature is incompatible with ESP8266") + required = esp8266_arduino + else: + raise NotImplementedError + if core_data[KEY_FRAMEWORK_VERSION] < required: + raise Invalid( + f"This feature requires at least framework version {required}" + ) + return value + + return validator + + +@contextmanager +def suppress_invalid(): + try: + yield + except vol.Invalid: + pass diff --git a/esphome/const.py b/esphome/const.py index 598b6351b4..6a3296bcea 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,25 +2,11 @@ __version__ = "2021.10.0-dev" -ESP_PLATFORM_ESP32 = "ESP32" -ESP_PLATFORM_ESP8266 = "ESP8266" -ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] - ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" -# Lookup table from ESP32 arduino framework version to latest platformio -# package with that version -# See also https://github.com/platformio/platform-espressif32/releases -ARDUINO_VERSION_ESP32 = { - "dev": "https://github.com/platformio/platform-espressif32.git", - "1.0.6": "platformio/espressif32@3.2.0", - "1.0.5": "platformio/espressif32@3.1.1", - "1.0.4": "platformio/espressif32@3.0.0", - "1.0.3": "platformio/espressif32@1.10.0", - "1.0.2": "platformio/espressif32@1.9.0", - "1.0.1": "platformio/espressif32@1.7.0", - "1.0.0": "platformio/espressif32@1.5.0", -} +TARGET_PLATFORMS = ["esp32", "esp8266"] +TARGET_FRAMEWORKS = ["arduino", "esp-idf"] + # See also https://github.com/platformio/platform-espressif8266/releases ARDUINO_VERSION_ESP8266 = { "dev": "https://github.com/platformio/platform-espressif8266.git", @@ -204,13 +190,11 @@ CONF_ECO2 = "eco2" CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" -CONF_ENABLE_MDNS = "enable_mdns" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_ID = "entity_id" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" -CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" CONF_EVENT = "event" @@ -253,6 +237,7 @@ CONF_FORCE_UPDATE = "force_update" CONF_FORMALDEHYDE = "formaldehyde" CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" +CONF_FRAMEWORK = "framework" CONF_FREQUENCY = "frequency" CONF_FROM = "from" CONF_FULL_SPECTRUM = "full_spectrum" @@ -307,6 +292,7 @@ CONF_INFRARED = "infrared" CONF_INITIAL_MODE = "initial_mode" CONF_INITIAL_OPTION = "initial_option" CONF_INITIAL_VALUE = "initial_value" +CONF_INPUT = "input" CONF_INTEGRATION_TIME = "integration_time" CONF_INTENSITY = "intensity" CONF_INTERLOCK = "interlock" @@ -447,6 +433,7 @@ CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" CONF_OPEN_ACTION = "open_action" +CONF_OPEN_DRAIN = "open_drain" CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" CONF_OPEN_DURATION = "open_duration" CONF_OPEN_ENDSTOP = "open_endstop" @@ -520,6 +507,8 @@ CONF_PRIORITY = "priority" CONF_PROJECT = "project" CONF_PROTOCOL = "protocol" CONF_PULL_MODE = "pull_mode" +CONF_PULLDOWN = "pulldown" +CONF_PULLUP = "pullup" CONF_PULSE_LENGTH = "pulse_length" CONF_QOS = "qos" CONF_RADON = "radon" @@ -890,3 +879,8 @@ STATE_CLASS_MEASUREMENT = "measurement" # The state represents a total that only increases, a decrease is considered a reset. STATE_CLASS_TOTAL_INCREASING = "total_increasing" + +KEY_CORE = "core" +KEY_TARGET_PLATFORM = "target_platform" +KEY_TARGET_FRAMEWORK = "target_framework" +KEY_FRAMEWORK_VERSION = "framework_version" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index e89f64d14c..9ec7fe358d 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,16 +2,17 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union from esphome.const import ( - CONF_ARDUINO_VERSION, CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_ETHERNET, CONF_WIFI, - SOURCE_FILE_EXTENSIONS, + KEY_CORE, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, ) from esphome.coroutine import FakeAwaitable as _FakeAwaitable from esphome.coroutine import FakeEventLoop as _FakeEventLoop @@ -20,7 +21,6 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop from esphome.coroutine import coroutine, coroutine_with_priority # noqa from esphome.helpers import ensure_unique_string, is_hassio from esphome.util import OrderedDict -from esphome import boards if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement @@ -441,19 +441,6 @@ class Library: return NotImplemented -def find_source_files(file): - files = set() - directory = os.path.abspath(os.path.dirname(file)) - for f in os.listdir(directory): - if not os.path.isfile(os.path.join(directory, f)): - continue - _, ext = os.path.splitext(f) - if ext.lower() not in SOURCE_FILE_EXTENSIONS: - continue - files.add(f) - return files - - # pylint: disable=too-many-instance-attributes,too-many-public-methods class EsphomeCore: def __init__(self): @@ -464,16 +451,13 @@ class EsphomeCore: self.ace = False # The name of the node self.name: Optional[str] = None + # Additional data components can store temporary data in + # The first key to this dict should always be the integration name + self.data = {} # The relative path to the configuration YAML self.config_path: Optional[str] = None # The relative path to where all build files are stored self.build_path: Optional[str] = None - # The platform (ESP8266, ESP32) of this device - self.esp_platform: Optional[str] = None - # The board that's used (for example nodemcuv2) - self.board: Optional[str] = None - # The full raw configuration - self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) @@ -494,6 +478,8 @@ class EsphomeCore: self.build_flags: Set[str] = set() # A set of defines to set for the compile process in esphome/core/defines.h self.defines: Set["Define"] = set() + # A map of all platformio options to apply + self.platformio_options: Dict[str, Union[str, List[str]]] = {} # A set of strings of names of loaded integrations, used to find namespace ID conflicts self.loaded_integrations = set() # A set of component IDs to track what Component subclasses are declared @@ -504,11 +490,9 @@ class EsphomeCore: def reset(self): self.dashboard = False self.name = None + self.data = {} self.config_path = None self.build_path = None - self.esp_platform = None - self.board = None - self.raw_config = None self.config = None self.event_loop = _FakeEventLoop() self.task_counter = 0 @@ -518,6 +502,7 @@ class EsphomeCore: self.libraries = [] self.build_flags = set() self.defines = set() + self.platformio_options = {} self.loaded_integrations = set() self.component_ids = set() @@ -544,13 +529,6 @@ class EsphomeCore: return None - @property - def arduino_version(self) -> str: - if self.config is None: - raise ValueError("Config has not been loaded yet") - - return self.config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - @property def config_dir(self): return os.path.dirname(self.config_path) @@ -586,27 +564,29 @@ class EsphomeCore: def firmware_bin(self): return self.relative_pioenvs_path(self.name, "firmware.bin") + @property + def target_platform(self): + return self.data[KEY_CORE][KEY_TARGET_PLATFORM] + @property def is_esp8266(self): - if self.esp_platform is None: - raise ValueError("No platform specified") - return self.esp_platform == "ESP8266" + return self.target_platform == "esp8266" @property def is_esp32(self): - """Check if the ESP32 platform is used. - - This checks if the ESP32 platform is in use, which - support ESP32 as well as other chips such as ESP32-C3 - """ - if self.esp_platform is None: - raise ValueError("No platform specified") - return self.esp_platform == "ESP32" + return self.target_platform == "esp32" @property - def is_esp32_c3(self): - """Check if the ESP32-C3 SoC is being used.""" - return self.is_esp32 and self.board in boards.ESP32_C3_BOARD_PINS + def target_framework(self): + return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] + + @property + def using_arduino(self): + return self.target_framework == "arduino" + + @property + def using_esp_idf(self): + return self.target_framework == "esp-idf" def add_job(self, func, *args, **kwargs): self.event_loop.add_job(func, *args, **kwargs) @@ -703,6 +683,14 @@ class EsphomeCore: _LOGGER.debug("Adding define: %s", define) return define + def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None: + new_val = value + old_val = self.platformio_options.get(key) + if isinstance(old_val, list): + assert isinstance(value, list) + new_val = old_val + value + self.platformio_options[key] = new_val + def _get_variable_generator(self, id): while True: try: diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index d59ad23a5e..a4d61f819c 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -1,7 +1,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/version.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #ifdef USE_STATUS_LED #include "esphome/components/status_led/status_led.h" @@ -60,9 +60,6 @@ void Application::setup() { ESP_LOGI(TAG, "setup() finished successfully!"); this->schedule_dump_config(); this->calculate_looping_components_(); - - // Dummy function to link some symbols into the binary. - force_link_symbols(); } void Application::loop() { uint32_t new_app_state = 0; @@ -110,11 +107,11 @@ void Application::loop() { } } -void ICACHE_RAM_ATTR HOT Application::feed_wdt() { +void IRAM_ATTR HOT Application::feed_wdt() { static uint32_t last_feed = 0; uint32_t now = millis(); if (now - last_feed > 3) { - this->feed_wdt_arch_(); + arch_feed_wdt(); last_feed = now; #ifdef USE_STATUS_LED if (status_led::global_status_led != nullptr) { @@ -127,11 +124,7 @@ void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot..."); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) - // restart() doesn't always end execution - while (true) { - yield(); - } + arch_restart(); } void Application::safe_reboot() { ESP_LOGI(TAG, "Rebooting safely..."); @@ -139,11 +132,7 @@ void Application::safe_reboot() { comp->on_safe_shutdown(); for (auto *comp : this->components_) comp->on_shutdown(); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) - // restart() doesn't always end execution - while (true) { - yield(); - } + arch_restart(); } void Application::calculate_looping_components_() { diff --git a/esphome/core/application.h b/esphome/core/application.h index e5f686a320..5c1483d301 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -51,7 +51,6 @@ class Application { this->name_ = name; } this->compilation_time_ = compilation_time; - global_preferences.begin(); } #ifdef USE_BINARY_SENSOR diff --git a/esphome/core/application_esp32.cpp b/esphome/core/application_esp32.cpp deleted file mode 100644 index 9f084428bb..0000000000 --- a/esphome/core/application_esp32.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP32 - -namespace esphome { - -static const char *const TAG = "app_esp32"; - -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { -#if CONFIG_ARDUINO_RUNNING_CORE == 0 -#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 - // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. - // To cause the Watchdog to be triggered we need to put the current task - // to sleep to get the idle task scheduled. - delay(1); -#endif -#endif -} - -} // namespace esphome -#endif diff --git a/esphome/core/application_esp8266.cpp b/esphome/core/application_esp8266.cpp deleted file mode 100644 index ee32417634..0000000000 --- a/esphome/core/application_esp8266.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP8266 - -namespace esphome { - -static const char *const TAG = "app_esp8266"; - -void ICACHE_RAM_ATTR HOT Application::feed_wdt_arch_() { - ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance) -} - -} // namespace esphome -#endif diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index e3b32978cd..c85b445b08 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -1,7 +1,7 @@ #include "esphome/core/component.h" #include "esphome/core/application.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -144,7 +144,7 @@ void Component::status_momentary_error(const std::string &name, uint32_t length) } void Component::dump_config() {} float Component::get_actual_setup_priority() const { - if (isnan(this->setup_priority_override_)) + if (std::isnan(this->setup_priority_override_)) return this->get_setup_priority(); return this->setup_priority_override_; } diff --git a/esphome/core/component.h b/esphome/core/component.h index 2654504fe8..85256c0f0f 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -2,7 +2,7 @@ #include #include -#include "Arduino.h" +#include #include "esphome/core/optional.h" diff --git a/esphome/core/config.py b/esphome/core/config.py index 222a775ee6..71add56b13 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -4,7 +4,7 @@ import re import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation, boards +from esphome import automation from esphome.const import ( CONF_ARDUINO_VERSION, CONF_BOARD, @@ -12,6 +12,7 @@ from esphome.const import ( CONF_BUILD_PATH, CONF_COMMENT, CONF_ESPHOME, + CONF_FRAMEWORK, CONF_INCLUDES, CONF_LIBRARIES, CONF_NAME, @@ -23,11 +24,10 @@ from esphome.const import ( CONF_PRIORITY, CONF_PROJECT, CONF_TRIGGER_ID, - CONF_ESP8266_RESTORE_FROM_FLASH, - ARDUINO_VERSION_ESP8266, - ARDUINO_VERSION_ESP32, + CONF_TYPE, CONF_VERSION, - ESP_PLATFORMS, + KEY_CORE, + TARGET_PLATFORMS, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -50,79 +50,6 @@ VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" -def validate_board(value: str): - if CORE.is_esp8266: - boardlist = boards.ESP8266_BOARD_PINS.keys() - elif CORE.is_esp32: - boardlist = list(boards.ESP32_BOARD_PINS.keys()) - boardlist += list(boards.ESP32_C3_BOARD_PINS.keys()) - else: - raise NotImplementedError - - if value not in boardlist: - raise cv.Invalid( - f"Could not find board '{value}'. Valid boards are {', '.join(sorted(boardlist))}" - ) - return value - - -validate_platform = cv.one_of(*ESP_PLATFORMS, upper=True) - -PLATFORMIO_ESP8266_LUT = { - **ARDUINO_VERSION_ESP8266, - # Keep this in mind when updating the recommended version: - # * New framework historically have had some regressions, especially for WiFi, BLE and the - # bootloader system. The new version needs to be thoroughly validated before changing the - # recommended version as otherwise a bunch of devices could be bricked - # * The docker images need to be updated to ship the new recommended version, in order not - # to DDoS platformio servers. - # Update this file: https://github.com/esphome/esphome-docker-base/blob/main/platformio.ini - "RECOMMENDED": ARDUINO_VERSION_ESP8266["2.7.4"], - "LATEST": "espressif8266", - "DEV": ARDUINO_VERSION_ESP8266["dev"], -} - -PLATFORMIO_ESP32_LUT = { - **ARDUINO_VERSION_ESP32, - # See PLATFORMIO_ESP8266_LUT for considerations when changing the recommended version - "RECOMMENDED": ARDUINO_VERSION_ESP32["1.0.6"], - "LATEST": "espressif32", - "DEV": ARDUINO_VERSION_ESP32["dev"], -} - - -def validate_arduino_version(value): - value = cv.string_strict(value) - value_ = value.upper() - if CORE.is_esp8266: - if ( - VERSION_REGEX.match(value) is not None - and value_ not in PLATFORMIO_ESP8266_LUT - ): - raise cv.Invalid( - f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif8266@" - ) - if value_ in PLATFORMIO_ESP8266_LUT: - return PLATFORMIO_ESP8266_LUT[value_] - return value - if CORE.is_esp32: - if ( - VERSION_REGEX.match(value) is not None - and value_ not in PLATFORMIO_ESP32_LUT - ): - raise cv.Invalid( - f"Unfortunately the arduino framework version '{value}' is unsupported at this time. You can override this by manually using espressif32@" - ) - if value_ in PLATFORMIO_ESP32_LUT: - return PLATFORMIO_ESP32_LUT[value_] - return value - raise NotImplementedError - - -def default_build_path(): - return CORE.name - - VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -149,27 +76,17 @@ def valid_project_name(value: str): return value +CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.hostname, - cv.Required(CONF_PLATFORM): cv.one_of("ESP8266", "ESP32", upper=True), - cv.Required(CONF_BOARD): validate_board, cv.Optional(CONF_COMMENT): cv.string, - cv.Optional( - CONF_ARDUINO_VERSION, default="recommended" - ): validate_arduino_version, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, + cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( { cv.string_strict: cv.Any([cv.string], cv.string), } ), - cv.SplitDefault(CONF_ESP8266_RESTORE_FROM_FLASH, esp8266=False): cv.All( - cv.only_on_esp8266, cv.boolean - ), - cv.SplitDefault(CONF_BOARD_FLASH_MODE, esp8266="dout"): cv.one_of( - *BUILD_FLASH_MODES, lower=True - ), cv.Optional(CONF_ON_BOOT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), @@ -201,38 +118,78 @@ CONFIG_SCHEMA = cv.Schema( PRELOAD_CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, - cv.Required(CONF_PLATFORM): validate_platform, + cv.Optional(CONF_BUILD_PATH): cv.string, + # Compat options, these were moved to target-platform specific sections + # but we'll keep these around for a long time because every config would + # be impacted + cv.Optional(CONF_PLATFORM): cv.one_of(*TARGET_PLATFORMS, lower=True), + cv.Optional(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_ESP8266_RESTORE_FROM_FLASH): cv.valid, + cv.Optional(CONF_BOARD_FLASH_MODE): cv.valid, + cv.Optional(CONF_ARDUINO_VERSION): cv.valid, }, extra=cv.ALLOW_EXTRA, ) -PRELOAD_CONFIG_SCHEMA2 = PRELOAD_CONFIG_SCHEMA.extend( - { - cv.Required(CONF_BOARD): validate_board, - cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, - } -) +def preload_core_config(config, result): + with cv.prepend_path(CONF_ESPHOME): + conf = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) -def preload_core_config(config): - core_key = "esphome" - if "esphomeyaml" in config: - _LOGGER.warning( - "The esphomeyaml section has been renamed to esphome in 1.11.0. " - "Please replace 'esphomeyaml:' in your configuration with 'esphome:'." + CORE.name = conf[CONF_NAME] + CORE.data[KEY_CORE] = {} + + if CONF_BUILD_PATH not in conf: + conf[CONF_BUILD_PATH] = CORE.name + CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) + + has_oldstyle = CONF_PLATFORM in conf + newstyle_found = [key for key in TARGET_PLATFORMS if key in config] + oldstyle_opts = [ + CONF_ESP8266_RESTORE_FROM_FLASH, + CONF_BOARD_FLASH_MODE, + CONF_ARDUINO_VERSION, + CONF_BOARD, + ] + + if not has_oldstyle and not newstyle_found: + raise cv.Invalid("Platform missing for core options!", [CONF_ESPHOME]) + if has_oldstyle and newstyle_found: + raise cv.Invalid( + f"Please remove the `platform` key from the [esphome] block. You're already using the new style with the [{conf[CONF_PLATFORM]}] block", + [CONF_ESPHOME, CONF_PLATFORM], ) - config[CONF_ESPHOME] = config.pop("esphomeyaml") - core_key = "esphomeyaml" - if CONF_ESPHOME not in config: - raise cv.RequiredFieldInvalid("required key not provided", CONF_ESPHOME) - with cv.prepend_path(core_key): - out = PRELOAD_CONFIG_SCHEMA(config[CONF_ESPHOME]) - CORE.name = out[CONF_NAME] - CORE.esp_platform = out[CONF_PLATFORM] - with cv.prepend_path(core_key): - out2 = PRELOAD_CONFIG_SCHEMA2(config[CONF_ESPHOME]) - CORE.board = out2[CONF_BOARD] - CORE.build_path = CORE.relative_config_path(out2[CONF_BUILD_PATH]) + if len(newstyle_found) > 1: + raise cv.Invalid( + f"Found multiple target platform blocks: {', '.join(newstyle_found)}. Only one is allowed.", + [newstyle_found[0]], + ) + if newstyle_found: + # Convert to newstyle + for key in oldstyle_opts: + if key in conf: + raise cv.Invalid( + f"Please move {key} to the [{newstyle_found[0]}] block.", + [CONF_ESPHOME, key], + ) + + if has_oldstyle: + plat = conf.pop(CONF_PLATFORM) + plat_conf = {} + if CONF_ESP8266_RESTORE_FROM_FLASH in conf: + plat_conf["restore_from_flash"] = conf.pop(CONF_ESP8266_RESTORE_FROM_FLASH) + if CONF_BOARD_FLASH_MODE in conf: + plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) + if CONF_ARDUINO_VERSION in conf: + plat_conf[CONF_FRAMEWORK] = { + CONF_TYPE: "arduino", + CONF_VERSION: conf.pop(CONF_ARDUINO_VERSION), + } + if CONF_BOARD in conf: + plat_conf[CONF_BOARD] = conf.pop(CONF_BOARD) + # Insert generated target platform config to main config + config[plat] = plat_conf + config[CONF_ESPHOME] = conf def include_file(path, basename): @@ -263,25 +220,10 @@ async def add_includes(includes): @coroutine_with_priority(-1000.0) -async def _esp8266_add_lwip_type(): - # If any component has already set this, do not change it - if any( - flag.startswith("-DPIO_FRAMEWORK_ARDUINO_LWIP2_") for flag in CORE.build_flags - ): - return - - # Default for platformio is LWIP2_LOW_MEMORY with: - # - MSS=536 - # - LWIP_FEATURES enabled - # - this only adds some optional features like IP incoming packet reassembly and NAPT - # see also: - # https://github.com/esp8266/Arduino/blob/master/tools/sdk/lwip2/include/lwipopts.h - - # Instead we use LWIP2_HIGHER_BANDWIDTH_LOW_FLASH with: - # - MSS=1460 - # - LWIP_FEATURES disabled (because we don't need them) - # Other projects like Tasmota & ESPEasy also use this - cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH") +async def _add_platformio_options(pio_options): + # Add includes at the very end, so that they override everything + for key, val in pio_options.items(): + cg.add_platformio_option(key, val) @coroutine_with_priority(30.0) @@ -315,10 +257,6 @@ async def to_code(config): CORE.add_job(_add_automations, config) - # Set LWIP build constants for ESP8266 - if CORE.is_esp8266: - CORE.add_job(_esp8266_add_lwip_type) - cg.add_build_flag("-fno-exceptions") # Libraries @@ -337,25 +275,16 @@ async def to_code(config): else: cg.add_library(lib, None) - if CORE.is_esp8266: - # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when - # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make - # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of - # a NULL pointer (so the stacktrace makes more sense), and for consistency with Arduino 3, - # which always aborts if exceptions are disabled. - # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` - cg.add_build_flag("-DNEW_OOM_ABORT") - cg.add_build_flag("-Wno-unused-variable") cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") - if config.get(CONF_ESP8266_RESTORE_FROM_FLASH, False): - cg.add_define("USE_ESP8266_PREFERENCES_FLASH") if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) - cg.add_define("ESPHOME_BOARD", CORE.board) if CONF_PROJECT in config: cg.add_define("ESPHOME_PROJECT_NAME", config[CONF_PROJECT][CONF_NAME]) cg.add_define("ESPHOME_PROJECT_VERSION", config[CONF_PROJECT][CONF_VERSION]) + + if config[CONF_PLATFORMIO_OPTIONS]: + CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 89010ce246..ac40921a4a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -13,7 +13,9 @@ // Feature flags #define USE_API #define USE_BINARY_SENSOR +#ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL +#endif #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP @@ -21,13 +23,17 @@ #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME -#define USE_HTTP_REQUEST_ESP8266_HTTPS -#define USE_I2C_MULTIPLEXER + +#ifdef USE_ARDUINO #define USE_JSON +#define USE_NEXTION_TFT_UPLOAD +#define USE_MQTT +#define USE_CAPTIVE_PORTAL +#endif // USE_ARDUINO + #define USE_LIGHT #define USE_LOGGER #define USE_MDNS -#define USE_MQTT #define USE_NUMBER #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY @@ -37,22 +43,29 @@ #define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT_SENSOR -#define USE_TFT_UPLOAD + #define USE_TIME #define USE_WIFI +#ifdef USE_ARDUINO #define USE_WIFI_WPA2_EAP - -#ifdef ARDUINO_ARCH_ESP32 -#define USE_ESP32_BLE_SERVER -#define USE_ESP32_CAMERA -#define USE_ETHERNET -#define USE_IMPROV -#define USE_SOCKET_IMPL_BSD_SOCKETS #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP32 +#define USE_ESP32_BLE_SERVER +#define USE_ESP32_CAMERA + +#ifdef USE_ARDUINO +#define USE_ETHERNET +#endif // USE_ARDUINO + +#define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS +#endif // USE_ESP32 + +#ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC #define USE_SOCKET_IMPL_LWIP_TCP +#define USE_HTTP_REQUEST_ESP8266_HTTPS #endif #define USE_API_PLAINTEXT @@ -60,3 +73,9 @@ // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. +#define USE_TIME +#define USE_DEEP_SLEEP +#define ESPHOME_BOARD "dummy_board" +#define USE_MDNS +#define USE_API_NOISE +#define USE_API_PLAINTEXT diff --git a/esphome/core/esphal.cpp b/esphome/core/esphal.cpp deleted file mode 100644 index 849fdf95ad..0000000000 --- a/esphome/core/esphal.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "esphome/core/esphal.h" -#include "esphome/core/macros.h" -#include "esphome/core/helpers.h" -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -typedef struct { // NOLINT - void *interruptInfo; // NOLINT - void *functionInfo; // NOLINT -} ArgStructure; - -void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void *), void *fp, // NOLINT - int mode); -void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin); // NOLINT -}; -#endif - -namespace esphome { - -static const char *const TAG = "esphal"; - -GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted) - : pin_(pin), - mode_(mode), - inverted_(inverted), -#ifdef ARDUINO_ARCH_ESP8266 - gpio_read_(pin < 16 ? &GPI : &GP16I), - gpio_mask_(pin < 16 ? (1UL << pin) : 1) -#elif ARDUINO_ARCH_ESP32 -#ifdef CONFIG_IDF_TARGET_ESP32C3 - gpio_set_(&GPIO.out_w1ts.val), - gpio_clear_(&GPIO.out_w1tc.val), - gpio_read_(&GPIO.in.val), -#else - gpio_set_(pin < 32 ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val), - gpio_clear_(pin < 32 ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val), - gpio_read_(pin < 32 ? &GPIO.in : &GPIO.in1.val), -#endif - gpio_mask_(pin < 32 ? (1UL << pin) : (1UL << (pin - 32))) -#endif -{ -} - -const LogString *GPIOPin::get_pin_mode_name() const { - const char *mode_s; - switch (this->mode_) { - case INPUT: - return LOG_STR("INPUT"); - case OUTPUT: - return LOG_STR("OUTPUT"); - case INPUT_PULLUP: - return LOG_STR("INPUT_PULLUP"); - case OUTPUT_OPEN_DRAIN: - return LOG_STR("OUTPUT_OPEN_DRAIN"); - case SPECIAL: - return LOG_STR("SPECIAL"); - case FUNCTION_1: - return LOG_STR("FUNCTION_1"); - case FUNCTION_2: - return LOG_STR("FUNCTION_2"); - case FUNCTION_3: - return LOG_STR("FUNCTION_3"); - case FUNCTION_4: - return LOG_STR("FUNCTION_4"); -#ifdef ARDUINO_ARCH_ESP32 - case PULLUP: - return LOG_STR("PULLUP"); - case PULLDOWN: - return LOG_STR("PULLDOWN"); - case INPUT_PULLDOWN: - return LOG_STR("INPUT_PULLDOWN"); - case OPEN_DRAIN: - return LOG_STR("OPEN_DRAIN"); - case FUNCTION_5: - return LOG_STR("FUNCTION_5"); - case FUNCTION_6: - return LOG_STR("FUNCTION_6"); - case ANALOG: - return LOG_STR("ANALOG"); -#endif -#ifdef ARDUINO_ARCH_ESP8266 - case FUNCTION_0: - return LOG_STR("FUNCTION_0"); - case WAKEUP_PULLUP: - return LOG_STR("WAKEUP_PULLUP"); - case WAKEUP_PULLDOWN: - return LOG_STR("WAKEUP_PULLDOWN"); - case INPUT_PULLDOWN_16: - return LOG_STR("INPUT_PULLDOWN_16"); -#endif - default: - return LOG_STR("UNKNOWN"); - } -} - -unsigned char GPIOPin::get_pin() const { return this->pin_; } -unsigned char GPIOPin::get_mode() const { return this->mode_; } - -bool GPIOPin::is_inverted() const { return this->inverted_; } -void GPIOPin::setup() { this->pin_mode(this->mode_); } -bool ICACHE_RAM_ATTR HOT GPIOPin::digital_read() { - return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; -} -bool ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_read() { - return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_; -} -void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ != 16) { - if (value != this->inverted_) { - GPOS = this->gpio_mask_; - } else { - GPOC = this->gpio_mask_; - } - } else { - if (value != this->inverted_) { - GP16O |= 1; - } else { - GP16O &= ~1; - } - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (value != this->inverted_) { - (*this->gpio_set_) = this->gpio_mask_; - } else { - (*this->gpio_clear_) = this->gpio_mask_; - } -#endif -} -void ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_write(bool value) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ != 16) { - if (value != this->inverted_) { - GPOS = this->gpio_mask_; - } else { - GPOC = this->gpio_mask_; - } - } else { - if (value != this->inverted_) { - GP16O |= 1; - } else { - GP16O &= ~1; - } - } -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (value != this->inverted_) { - (*this->gpio_set_) = this->gpio_mask_; - } else { - (*this->gpio_clear_) = this->gpio_mask_; - } -#endif -} -ISRInternalGPIOPin::ISRInternalGPIOPin(uint8_t pin, -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, -#endif - volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted) - : pin_(pin), - inverted_(inverted), - gpio_read_(gpio_read), - gpio_mask_(gpio_mask) -#ifdef ARDUINO_ARCH_ESP32 - , - gpio_clear_(gpio_clear), - gpio_set_(gpio_set) -#endif -{ -} -void ICACHE_RAM_ATTR ISRInternalGPIOPin::clear_interrupt() { -#ifdef ARDUINO_ARCH_ESP8266 - GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->gpio_mask_); -#endif -#ifdef ARDUINO_ARCH_ESP32 -#ifdef CONFIG_IDF_TARGET_ESP32C3 - GPIO.status_w1tc.val = this->gpio_mask_; -#else - if (this->pin_ < 32) { - GPIO.status_w1tc = this->gpio_mask_; - } else { - GPIO.status1_w1tc.intr_st = this->gpio_mask_; - } -#endif -#endif -} - -void ICACHE_RAM_ATTR HOT GPIOPin::pin_mode(uint8_t mode) { -#ifdef ARDUINO_ARCH_ESP8266 - if (this->pin_ == 16 && mode == INPUT_PULLUP) { - // pullups are not available on GPIO16, manually override with - // input mode. - pinMode(16, INPUT); - return; - } -#endif - pinMode(this->pin_, mode); -} - -#ifdef ARDUINO_ARCH_ESP8266 -struct ESPHomeInterruptFuncInfo { - void (*func)(void *); - void *arg; -}; - -void ICACHE_RAM_ATTR interrupt_handler(void *arg) { - ArgStructure *as = static_cast(arg); - auto *info = static_cast(as->functionInfo); - info->func(info->arg); -} -#endif - -void GPIOPin::detach_interrupt() const { this->detach_interrupt_(); } -void GPIOPin::detach_interrupt_() const { -#ifdef ARDUINO_ARCH_ESP8266 - __detachInterrupt(get_pin()); -#endif -#ifdef ARDUINO_ARCH_ESP32 - detachInterrupt(get_pin()); -#endif -} -void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const { - if (this->inverted_) { - if (mode == RISING) { - mode = FALLING; - } else if (mode == FALLING) { - mode = RISING; - } - } -#ifdef ARDUINO_ARCH_ESP8266 - ArgStructure *as = new ArgStructure; // NOLINT - as->interruptInfo = nullptr; - - as->functionInfo = new ESPHomeInterruptFuncInfo{ - // NOLINT - .func = func, - .arg = arg, - }; - - __attachInterruptArg(this->pin_, interrupt_handler, as, mode); -#endif -#ifdef ARDUINO_ARCH_ESP32 - // work around issue https://github.com/espressif/arduino-esp32/pull/1776 in arduino core - // yet again proves how horrible code is there :( - how could that have been accepted... - auto *attach = reinterpret_cast(attachInterruptArg); - attach(this->pin_, func, arg, mode); -#endif -} - -ISRInternalGPIOPin *GPIOPin::to_isr() const { - return new ISRInternalGPIOPin(this->pin_, // NOLINT -#ifdef ARDUINO_ARCH_ESP32 - this->gpio_clear_, this->gpio_set_, -#endif - this->gpio_read_, this->gpio_mask_, this->inverted_); -} - -void force_link_symbols() { -#ifdef ARDUINO_ARCH_ESP8266 - // Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible - // with their settings - ESPHome uses a different settings system (that can also survive - // erases). So set magic bytes indicating all tasmota versions are supported. - // This only adds 12 bytes of binary size, which is an acceptable price to pay for easier support - // for Tasmota. - // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/settings.ino#L346-L380 - // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/i18n.h#L652-L654 - const static uint32_t TASMOTA_MAGIC_BYTES[] PROGMEM = {0x5AA55AA5, 0xFFFFFFFF, 0xA55AA55A}; - // Force link symbol by using a volatile integer (GCC attribute used does not work because of LTO) - volatile int x = 0; - x = TASMOTA_MAGIC_BYTES[x]; -#endif -} - -} // namespace esphome - -#if defined(ARDUINO_ARCH_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -// Fix 2.3.0 std missing memchr -extern "C" { -void *memchr(const void *s, int c, size_t n) { - if (n == 0) - return nullptr; - const uint8_t *p = reinterpret_cast(s); - do { - if (*p++ == c) - return const_cast(reinterpret_cast(p - 1)); - } while (--n != 0); - return nullptr; -} -}; -#endif - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -extern void resetPins() { // NOLINT - // Added in framework 2.7.0 - // usually this sets up all pins to be in INPUT mode - // however, not strictly needed as we set up the pins properly - // ourselves and this causes pins to toggle during reboot. -} -} -#endif diff --git a/esphome/core/esphal.h b/esphome/core/esphal.h deleted file mode 100644 index 1b92f816b1..0000000000 --- a/esphome/core/esphal.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include "Arduino.h" - -// Fix some arduino defs -#ifdef round -#undef round -#endif -#ifdef bool -#undef bool -#endif -#ifdef true -#undef true -#endif -#ifdef false -#undef false -#endif -#ifdef min -#undef min -#endif -#ifdef max -#undef max -#endif -#ifdef abs -#undef abs -#endif - -#include "esphome/core/log.h" - -namespace esphome { - -#define LOG_PIN(prefix, pin) \ - if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix LOG_PIN_PATTERN, LOG_PIN_ARGS(pin)); \ - } -#define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)" -#define LOG_PIN_ARGS(pin) \ - (pin)->get_pin(), LOG_STR_ARG((pin)->get_pin_mode_name()), ((pin)->is_inverted() ? ", INVERTED" : "") - -/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) -class ISRInternalGPIOPin { - public: - ISRInternalGPIOPin(uint8_t pin, -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set, -#endif - volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted); - bool digital_read(); - void digital_write(bool value); - void clear_interrupt(); - - protected: - const uint8_t pin_; - const bool inverted_; - volatile uint32_t *const gpio_read_; - const uint32_t gpio_mask_; -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *const gpio_clear_; - volatile uint32_t *const gpio_set_; -#endif -}; - -/** A high-level abstraction class that can expose a pin together with useful options like pinMode. - * - * Set the parameters for this at construction time and use setup() to apply them. The inverted parameter will - * automatically invert the input/output for you. - * - * Use read_value() and write_value() to use digitalRead() and digitalWrite(), respectively. - */ -class GPIOPin { - public: - /** Construct the GPIOPin instance. - * - * @param pin The GPIO pin number of this instance. - * @param mode The Arduino pinMode that this pin should be put into at setup(). - * @param inverted Whether all digitalRead/digitalWrite calls should be inverted. - */ - GPIOPin(uint8_t pin, uint8_t mode, bool inverted = false); - - /// Setup the pin mode. - virtual void setup(); - /// Read the binary value from this pin using digitalRead (and inverts automatically). - virtual bool digital_read(); - /// Write the binary value to this pin using digitalWrite (and inverts automatically). - virtual void digital_write(bool value); - /// Set the pin mode - virtual void pin_mode(uint8_t mode); - - /// Get the GPIO pin number. - uint8_t get_pin() const; - const LogString *get_pin_mode_name() const; - /// Get the pinMode of this pin. - uint8_t get_mode() const; - /// Return whether this pin shall be treated as inverted. (for example active-low) - bool is_inverted() const; - - template void attach_interrupt(void (*func)(T *), T *arg, int mode) const; - void detach_interrupt() const; - - ISRInternalGPIOPin *to_isr() const; - - protected: - void attach_interrupt_(void (*func)(void *), void *arg, int mode) const; - void detach_interrupt_() const; - - const uint8_t pin_; - const uint8_t mode_; - const bool inverted_; -#ifdef ARDUINO_ARCH_ESP32 - volatile uint32_t *const gpio_set_; - volatile uint32_t *const gpio_clear_; -#endif - volatile uint32_t *const gpio_read_; - const uint32_t gpio_mask_; -}; - -template void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const { - this->attach_interrupt_(reinterpret_cast(func), arg, mode); -} -/** This function can be used by the HAL to force-link specific symbols - * into the generated binary without modifying the linker script. - * - * It is called by the application very early on startup and should not be used for anything - * other than forcing symbols to be linked. - */ -void force_link_symbols(); - -} // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h new file mode 100644 index 0000000000..25d56b9020 --- /dev/null +++ b/esphome/core/gpio.h @@ -0,0 +1,98 @@ +#pragma once +#include +#include + +namespace esphome { + +#define LOG_PIN(prefix, pin) \ + if ((pin) != nullptr) { \ + ESP_LOGCONFIG(TAG, prefix "%s", pin->dump_summary().c_str()); \ + } + +// put GPIO flags in a namepsace to not pollute esphome namespace +namespace gpio { + +enum Flags : uint8_t { + // Can't name these just INPUT because of Arduino defines :( + FLAG_NONE = 0x00, + FLAG_INPUT = 0x01, + FLAG_OUTPUT = 0x02, + FLAG_OPEN_DRAIN = 0x04, + FLAG_PULLUP = 0x08, + FLAG_PULLDOWN = 0x10, +}; + +class FlagsHelper { + public: + constexpr FlagsHelper(Flags val) : val_(val) {} + constexpr operator Flags() const { return val_; } + + protected: + Flags val_; +}; +constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +enum InterruptType : uint8_t { + INTERRUPT_RISING_EDGE = 1, + INTERRUPT_FALLING_EDGE = 2, + INTERRUPT_ANY_EDGE = 3, + INTERRUPT_LOW_LEVEL = 4, + INTERRUPT_HIGH_LEVEL = 5, +}; + +} // namespace gpio + +class GPIOPin { + public: + virtual void setup() = 0; + + virtual void pin_mode(gpio::Flags flags) = 0; + + virtual bool digital_read() = 0; + + virtual void digital_write(bool value) = 0; + + virtual std::string dump_summary() const = 0; + + virtual bool is_internal() { return false; } +}; + +/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) +class ISRInternalGPIOPin { + public: + ISRInternalGPIOPin() = default; + ISRInternalGPIOPin(void *arg) : arg_(arg) {} + bool digital_read(); + void digital_write(bool value); + void clear_interrupt(); + + protected: + void *arg_ = nullptr; +}; + +class InternalGPIOPin : public GPIOPin { + public: + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + + virtual void detach_interrupt() const = 0; + + virtual ISRInternalGPIOPin to_isr() const = 0; + + virtual uint8_t get_pin() const = 0; + + bool is_internal() override { return true; } + + virtual bool is_inverted() const = 0; + + protected: + virtual void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; +}; + +} // namespace esphome diff --git a/esphome/core/hal.h b/esphome/core/hal.h new file mode 100644 index 0000000000..2843bb9b15 --- /dev/null +++ b/esphome/core/hal.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include "gpio.h" + +#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) +#include +#ifndef PROGMEM +#define PROGMEM +#endif + +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) + +#include + +#ifndef PROGMEM +#define PROGMEM +#endif + +#elif defined(USE_ESP8266) + +#include +#ifndef PROGMEM +#define PROGMEM ICACHE_RODATA_ATTR +#endif + +#else + +#define IRAM_ATTR +#define PROGMEM + +#endif + +namespace esphome { + +void yield(); +uint32_t millis(); +uint32_t micros(); +void delay(uint32_t ms); +void delayMicroseconds(uint32_t us); +void __attribute__((noreturn)) arch_restart(); +void arch_feed_wdt(); +uint32_t arch_get_cpu_cycle_count(); +uint32_t arch_get_cpu_freq_hz(); +uint8_t progmem_read_byte(const uint8_t *addr); + +} // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 53c7d1de43..a90eb74be2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,15 +1,22 @@ #include "esphome/core/helpers.h" #include #include +#include +#include -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(USE_ESP8266) #include -#else +#include +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include +#elif defined(USE_ESP_IDF) +#include "esp_system.h" +#include +#include #endif #include "esphome/core/log.h" -#include "esphome/core/esphal.h" +#include "esphome/core/hal.h" namespace esphome { @@ -18,10 +25,10 @@ static const char *const TAG = "helpers"; std::string get_mac_address() { char tmp[20]; uint8_t mac[6]; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_efuse_mac_get_default(mac); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFi.macAddress(mac); #endif sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -31,10 +38,10 @@ std::string get_mac_address() { std::string get_mac_address_pretty() { char tmp[20]; uint8_t mac[6]; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 esp_efuse_mac_get_default(mac); #endif -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 WiFi.macAddress(mac); #endif sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -44,9 +51,9 @@ std::string get_mac_address_pretty() { std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } uint32_t random_uint32() { -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32 return esp_random(); -#else +#elif defined(USE_ESP8266) return os_random(); #endif } @@ -56,11 +63,13 @@ double random_double() { return random_uint32() / double(UINT32_MAX); } float random_float() { return float(random_double()); } void fill_random(uint8_t *data, size_t len) { -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) esp_fill_random(data, len); -#else +#elif defined(USE_ESP8266) int err = os_get_random(data, len); assert(err == 0); +#else +#error "No random source for this system config" #endif } @@ -123,10 +132,13 @@ std::string truncate_string(const std::string &s, size_t length) { } std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - auto multiplier = float(powf(10.0f, accuracy_decimals)); - float value_rounded = roundf(value * multiplier) / multiplier; + if (accuracy_decimals < 0) { + auto multiplier = powf(10.0f, accuracy_decimals); + value = roundf(value * multiplier) / multiplier; + accuracy_decimals = 0; + } char tmp[32]; // should be enough, but we should maybe improve this at some point. - dtostrf(value_rounded, 0, uint8_t(std::max(0, int(accuracy_decimals))), tmp); + snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); return std::string(tmp); } std::string uint64_to_string(uint64_t num) { @@ -334,13 +346,13 @@ std::string hexencode(const uint8_t *data, uint32_t len) { return res; } -#ifdef ARDUINO_ARCH_ESP8266 -ICACHE_RAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } -ICACHE_RAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#ifdef USE_ESP8266 +IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } +IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #endif -#ifdef ARDUINO_ARCH_ESP32 -ICACHE_RAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } -ICACHE_RAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } +IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6c0ee8399e..40d94005e7 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -6,7 +6,7 @@ #include #include -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include "esp32-hal-psram.h" #endif @@ -151,7 +151,7 @@ uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); * This behaves like std::lock_guard. As long as the value is visible in the current stack, all interrupts * (including flash reads) will be disabled. * - * Please note all functions called when the interrupt lock must be marked ICACHE_RAM_ATTR (loading code into + * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being * pulled from flash). * @@ -173,7 +173,7 @@ class InterruptLock { ~InterruptLock(); protected: -#ifdef ARDUINO_ARCH_ESP8266 +#ifdef USE_ESP8266 uint32_t xt_state_; #endif }; @@ -277,8 +277,8 @@ template class TemplatableValue { LAMBDA, } type_; - T value_; - std::function f_; + T value_{}; + std::function f_{}; }; template class TemplatableStringValue : public TemplatableValue { @@ -329,7 +329,7 @@ uint32_t fnv1_hash(const std::string &str); template T *new_buffer(size_t length) { T *buffer; -#ifdef ARDUINO_ARCH_ESP32 +#ifdef USE_ESP32_FRAMEWORK_ARDUINO if (psramFound()) { buffer = (T *) ps_malloc(length); } else { diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 9b49a4c6ba..424154d253 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -46,7 +46,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT #ifdef USE_LOGGER auto *log = logger::global_logger; diff --git a/esphome/core/log.h b/esphome/core/log.h index 3c2f1e6999..590ad26032 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -8,10 +8,14 @@ #include "WString.h" #endif -// Both the ESP-IDF and Arduino also define ESP_LOG* macros. Include them here, so that they won't -// be reincluded later on and redefine our macros. -#ifdef ARDUINO_ARCH_ESP32 +#include "esphome/core/macros.h" + +// Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#include #include +#endif +#ifdef USE_ESP32_FRAMEWORK_ARDUINO #include #endif @@ -58,7 +62,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format, #ifdef USE_STORE_LOG_STR_IN_FLASH void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); #endif -#ifdef ARDUINO_ARCH_ESP32 +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #endif diff --git a/esphome/core/macros.h b/esphome/core/macros.h index 59b52bf7a1..b0027a276c 100644 --- a/esphome/core/macros.h +++ b/esphome/core/macros.h @@ -2,7 +2,7 @@ #define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) -#if defined(ARDUINO_ARCH_ESP8266) +#if defined(USE_ESP8266) #include #if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ @@ -43,7 +43,7 @@ #warning "Could not determine Arduino framework version, update esphome/core/macros.h!" #endif -#elif defined(ARDUINO_ARCH_ESP32) +#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header #include diff --git a/esphome/core/preferences.cpp b/esphome/core/preferences.cpp deleted file mode 100644 index f051ce0e8a..0000000000 --- a/esphome/core/preferences.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "esphome/core/preferences.h" -#include "esphome/core/log.h" -#include "esphome/core/helpers.h" -#include "esphome/core/application.h" - -#ifdef ARDUINO_ARCH_ESP8266 -extern "C" { -#include "spi_flash.h" -} -#endif -#ifdef ARDUINO_ARCH_ESP32 -#include "nvs.h" -#include "nvs_flash.h" -#endif - -namespace esphome { - -static const char *const TAG = "preferences"; - -ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) - : offset_(offset), length_words_(length), type_(type), data_(length + 1) {} -bool ESPPreferenceObject::load_() { - if (!this->is_initialized()) { - ESP_LOGV(TAG, "Load Pref Not initialized!"); - return false; - } - if (!this->load_internal_()) - return false; - - bool valid = this->data_[this->length_words_] == this->calculate_crc_(); - - ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT - YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); - return valid; -} -bool ESPPreferenceObject::save_() { - if (!this->is_initialized()) { - ESP_LOGV(TAG, "Save Pref Not initialized!"); - return false; - } - - this->data_[this->length_words_] = this->calculate_crc_(); - if (!this->save_internal_()) - return false; - ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT - this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); - return true; -} - -#ifdef ARDUINO_ARCH_ESP8266 - -static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; -#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) -static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; -static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; - -#ifdef USE_ESP8266_PREFERENCES_FLASH -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; -#else -static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; -#endif - -static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { - if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { - return false; - } - *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) - return true; -} - -static bool esp8266_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { - if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { - return false; - } - if (index < 32 && global_preferences.is_prevent_write()) { - return false; - } - - auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) - *ptr = value; - return true; -} - -extern "C" uint32_t _SPIFFS_end; // NOLINT - -static const uint32_t get_esp8266_flash_sector() { - union { - uint32_t *ptr; - uint32_t uint; - } data{}; - data.ptr = &_SPIFFS_end; - return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; -} -static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } - -void ESPPreferences::save_esp8266_flash_() { - if (!esp8266_flash_dirty) - return; - - ESP_LOGVV(TAG, "Saving preferences to flash..."); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); - return; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); - return; - } - - esp8266_flash_dirty = false; -} - -bool ESPPreferenceObject::save_internal_() { - if (this->in_flash_) { - for (uint32_t i = 0; i <= this->length_words_; i++) { - uint32_t j = this->offset_ + i; - if (j >= ESP8266_FLASH_STORAGE_SIZE) - return false; - uint32_t v = this->data_[i]; - uint32_t *ptr = &global_preferences.flash_storage_[j]; - if (*ptr != v) - esp8266_flash_dirty = true; - *ptr = v; - } - global_preferences.save_esp8266_flash_(); - return true; - } - - for (uint32_t i = 0; i <= this->length_words_; i++) { - if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i])) - return false; - } - - return true; -} -bool ESPPreferenceObject::load_internal_() { - if (this->in_flash_) { - for (uint32_t i = 0; i <= this->length_words_; i++) { - uint32_t j = this->offset_ + i; - if (j >= ESP8266_FLASH_STORAGE_SIZE) - return false; - this->data_[i] = global_preferences.flash_storage_[j]; - } - - return true; - } - - for (uint32_t i = 0; i <= this->length_words_; i++) { - if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i])) - return false; - } - return true; -} -ESPPreferences::ESPPreferences() - // offset starts from start of user RTC mem (64 words before that are reserved for system), - // an additional 32 words at the start of user RTC are for eboot (OTA, see eboot_command.h), - // which will be reset each time OTA occurs - : current_offset_(0) {} - -void ESPPreferences::begin() { - this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT - ESP_LOGVV(TAG, "Loading preferences from flash..."); - - { - InterruptLock lock; - spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); - } -} - -ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { - if (in_flash) { - uint32_t start = this->current_flash_offset_; - uint32_t end = start + length + 1; - if (end > ESP8266_FLASH_STORAGE_SIZE) - return {}; - auto pref = ESPPreferenceObject(start, length, type); - pref.in_flash_ = true; - this->current_flash_offset_ = end; - return pref; - } - - uint32_t start = this->current_offset_; - uint32_t end = start + length + 1; - bool in_normal = start < 96; - // Normal: offset 0-95 maps to RTC offset 32 - 127, - // Eboot: offset 96-127 maps to RTC offset 0 - 31 words - if (in_normal && end > 96) { - // start is in normal but end is not -> switch to Eboot - this->current_offset_ = start = 96; - end = start + length + 1; - in_normal = false; - } - - if (end > 128) { - // Doesn't fit in data, return uninitialized preference obj. - return {}; - } - - uint32_t rtc_offset; - if (in_normal) { - rtc_offset = start + 32; - } else { - rtc_offset = start - 96; - } - - auto pref = ESPPreferenceObject(rtc_offset, length, type); - this->current_offset_ += length + 1; - return pref; -} -void ESPPreferences::prevent_write(bool prevent) { this->prevent_write_ = prevent; } -bool ESPPreferences::is_prevent_write() { return this->prevent_write_; } -#endif - -#ifdef ARDUINO_ARCH_ESP32 -bool ESPPreferenceObject::save_internal_() { - if (global_preferences.nvs_handle_ == 0) - return false; - - char key[32]; - sprintf(key, "%u", this->offset_); - uint32_t len = (this->length_words_ + 1) * 4; - esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_.data(), len); - if (err) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); - return false; - } - err = nvs_commit(global_preferences.nvs_handle_); - if (err) { - ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); - return false; - } - return true; -} -bool ESPPreferenceObject::load_internal_() { - if (global_preferences.nvs_handle_ == 0) - return false; - - char key[32]; - sprintf(key, "%u", this->offset_); - size_t len = (this->length_words_ + 1) * 4; - - size_t actual_len; - esp_err_t err = nvs_get_blob(global_preferences.nvs_handle_, key, nullptr, &actual_len); - if (err) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key, esp_err_to_name(err)); - return false; - } - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len); - return false; - } - err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_.data(), &len); - if (err) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err)); - return false; - } - return true; -} -ESPPreferences::ESPPreferences() : current_offset_(0) {} -void ESPPreferences::begin() { - auto ns = truncate_string(App.get_name(), 15); - esp_err_t err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); - if (err) { - ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err)); - nvs_flash_deinit(); - nvs_flash_erase(); - nvs_flash_init(); - - err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); - if (err) { - this->nvs_handle_ = 0; - } - } -} - -ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { - auto pref = ESPPreferenceObject(this->current_offset_, length, type); - this->current_offset_++; - return pref; -} -#endif -uint32_t ESPPreferenceObject::calculate_crc_() const { - uint32_t crc = this->type_; - for (size_t i = 0; i < this->length_words_; i++) { - crc ^= (this->data_[i] * 2654435769UL) >> 1; - } - return crc; -} -bool ESPPreferenceObject::is_initialized() const { return !this->data_.empty(); } - -ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -} // namespace esphome diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 7ed9a2c8c5..ff7911ed3a 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,112 +1,61 @@ #pragma once -#include -#include -#include #include - -#include "esphome/core/defines.h" +#include +#include namespace esphome { +class ESPPreferenceBackend { + public: + virtual bool save(const uint8_t *data, size_t len) = 0; + virtual bool load(uint8_t *data, size_t len) = 0; +}; + class ESPPreferenceObject { public: ESPPreferenceObject() = default; - ESPPreferenceObject(size_t offset, size_t length, uint32_t type); + ESPPreferenceObject(ESPPreferenceBackend *backend) : backend_(backend) {} - template bool save(T *src); + template bool save(const T *src) { + if (backend_ == nullptr) + return false; + return backend_->save(reinterpret_cast(src), sizeof(T)); + } - template bool load(T *dest); - - bool is_initialized() const; + template bool load(T *dest) { + if (backend_ == nullptr) + return false; + return backend_->load(reinterpret_cast(dest), sizeof(T)); + } protected: - friend class ESPPreferences; - - bool save_(); - bool load_(); - bool save_internal_(); - bool load_internal_(); - - uint32_t calculate_crc_() const; - - size_t offset_ = 0; - size_t length_words_ = 0; - uint32_t type_ = 0; - std::vector data_; -#ifdef ARDUINO_ARCH_ESP8266 - bool in_flash_ = false; -#endif + ESPPreferenceBackend *backend_; }; -#ifdef ARDUINO_ARCH_ESP8266 -#ifdef USE_ESP8266_PREFERENCES_FLASH -static const bool DEFAULT_IN_FLASH = true; -#else -static const bool DEFAULT_IN_FLASH = false; -#endif -#endif - -#ifdef ARDUINO_ARCH_ESP32 -static const bool DEFAULT_IN_FLASH = true; -#endif - class ESPPreferences { public: - ESPPreferences(); - void begin(); - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash = DEFAULT_IN_FLASH); - template ESPPreferenceObject make_preference(uint32_t type, bool in_flash = DEFAULT_IN_FLASH); - -#ifdef ARDUINO_ARCH_ESP8266 - /** On the ESP8266, we can't override the first 128 bytes during OTA uploads - * as the eboot parameters are stored there. Writing there during an OTA upload - * would invalidate applying the new firmware. During normal operation, we use - * this part of the RTC user memory, but stop writing to it during OTA uploads. - * - * @param prevent Whether to prevent writing to the first 32 words of RTC user memory. - */ - void prevent_write(bool prevent); - bool is_prevent_write(); + virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; + virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; +#ifndef USE_ESP8266 + template::value, bool>::type = true> +#else + // esp8266 toolchain doesn't have is_trivially_copyable + template #endif - - protected: - friend ESPPreferenceObject; - - uint32_t current_offset_; -#ifdef ARDUINO_ARCH_ESP32 - uint32_t nvs_handle_; -#endif -#ifdef ARDUINO_ARCH_ESP8266 - void save_esp8266_flash_(); - bool prevent_write_{false}; - uint32_t *flash_storage_; - uint32_t current_flash_offset_; + ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { + return this->make_preference(sizeof(T), type, in_flash); + } +#ifndef USE_ESP8266 + template::value, bool>::type = true> +#else + template #endif + ESPPreferenceObject make_preference(uint32_t type) { + return this->make_preference(sizeof(T), type); + } }; -extern ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -template ESPPreferenceObject ESPPreferences::make_preference(uint32_t type, bool in_flash) { - return this->make_preference((sizeof(T) + 3) / 4, type, in_flash); -} - -template bool ESPPreferenceObject::save(T *src) { - if (!this->is_initialized()) - return false; - // ensure all bytes are 0 (in case sizeof(T) is not multiple of 4) - std::fill_n(data_.begin(), length_words_, 0); - memcpy(data_.data(), src, sizeof(T)); - return this->save_(); -} - -template bool ESPPreferenceObject::load(T *dest) { - std::fill_n(data_.begin(), length_words_, 0); - if (!this->load_()) - return false; - - memcpy(dest, data_.data(), sizeof(T)); - return true; -} +extern ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 5718e3b396..a6d3e0307e 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -1,6 +1,7 @@ #include "scheduler.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" #include namespace esphome { @@ -81,7 +82,7 @@ optional HOT Scheduler::next_schedule_in() { return 0; return next_time - now; } -void ICACHE_RAM_ATTR HOT Scheduler::call() { +void IRAM_ATTR HOT Scheduler::call() { const uint32_t now = this->millis_(); this->process_to_add(); diff --git a/esphome/core/util.cpp b/esphome/core/util.cpp index e0132e2e4a..996cf8e310 100644 --- a/esphome/core/util.cpp +++ b/esphome/core/util.cpp @@ -4,47 +4,16 @@ #include "esphome/core/version.h" #include "esphome/core/log.h" -#ifdef USE_WIFI -#include "esphome/components/wifi/wifi_component.h" -#endif - #ifdef USE_API #include "esphome/components/api/api_server.h" #endif -#ifdef USE_ETHERNET -#include "esphome/components/ethernet/ethernet_component.h" -#endif - #ifdef USE_MQTT #include "esphome/components/mqtt/mqtt_client.h" #endif -#ifdef USE_MDNS -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif -#endif - namespace esphome { -bool network_is_connected() { -#ifdef USE_ETHERNET - if (ethernet::global_eth_component != nullptr && ethernet::global_eth_component->is_connected()) - return true; -#endif - -#ifdef USE_WIFI - if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->is_connected(); -#endif - - return false; -} - bool api_is_connected() { #ifdef USE_API if (api::global_api_server != nullptr) { @@ -65,78 +34,4 @@ bool mqtt_is_connected() { bool remote_is_connected() { return api_is_connected() || mqtt_is_connected(); } -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) -static bool mdns_setup; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -#endif - -#ifndef WEBSERVER_PORT -static const uint8_t WEBSERVER_PORT = 80; -#endif - -#ifdef USE_MDNS -#ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(const IPAddress &address, int interface) { - // Latest arduino framework breaks mDNS for AP interface - // see https://github.com/esp8266/Arduino/issues/6114 - if (interface == 1) - return; - MDNS.begin(App.get_name().c_str(), address); - mdns_setup = true; -#endif -#ifdef ARDUINO_ARCH_ESP32 - void network_setup_mdns() { - MDNS.begin(App.get_name().c_str()); -#endif -#ifdef USE_API - if (api::global_api_server != nullptr) { - MDNS.addService("esphomelib", "tcp", api::global_api_server->get_port()); - // DNS-SD (!=mDNS !) requires at least one TXT record for service discovery - let's add version - MDNS.addServiceTxt("esphomelib", "tcp", "version", ESPHOME_VERSION); - MDNS.addServiceTxt("esphomelib", "tcp", "address", network_get_address().c_str()); - MDNS.addServiceTxt("esphomelib", "tcp", "mac", get_mac_address().c_str()); -#ifdef ARDUINO_ARCH_ESP8266 - MDNS.addServiceTxt("esphomelib", "tcp", "platform", "ESP8266"); -#endif -#ifdef ARDUINO_ARCH_ESP32 - MDNS.addServiceTxt("esphomelib", "tcp", "platform", "ESP32"); -#endif - MDNS.addServiceTxt("esphomelib", "tcp", "board", ESPHOME_BOARD); -#ifdef ESPHOME_PROJECT_NAME - MDNS.addServiceTxt("esphomelib", "tcp", "project_name", ESPHOME_PROJECT_NAME); - MDNS.addServiceTxt("esphomelib", "tcp", "project_version", ESPHOME_PROJECT_VERSION); -#endif - } else { -#endif - // Publish "http" service if not using native API nor the webserver component - // This is just to have *some* mDNS service so that .local resolution works - MDNS.addService("http", "tcp", WEBSERVER_PORT); - MDNS.addServiceTxt("http", "tcp", "version", ESPHOME_VERSION); -#ifdef USE_API - } -#endif -#ifdef USE_PROMETHEUS - MDNS.addService("prometheus-http", "tcp", WEBSERVER_PORT); -#endif - } -#endif - - void network_tick_mdns() { -#if defined(ARDUINO_ARCH_ESP8266) && defined(USE_MDNS) - if (mdns_setup) - MDNS.update(); -#endif - } - - std::string network_get_address() { -#ifdef USE_ETHERNET - if (ethernet::global_eth_component != nullptr) - return ethernet::global_eth_component->get_use_address(); -#endif -#ifdef USE_WIFI - if (wifi::global_wifi_component != nullptr) - return wifi::global_wifi_component->get_use_address(); -#endif - return ""; - } - } // namespace esphome diff --git a/esphome/core/util.h b/esphome/core/util.h index 764c6aaf03..1ca0173eab 100644 --- a/esphome/core/util.h +++ b/esphome/core/util.h @@ -1,15 +1,8 @@ #pragma once #include -#include "IPAddress.h" - namespace esphome { -/// Return whether the node is connected to the network (through wifi, eth, ...) -bool network_is_connected(); -/// Get the active network hostname -std::string network_get_address(); - /// Return whether the node has at least one client connected to the native API bool api_is_connected(); @@ -19,14 +12,4 @@ bool mqtt_is_connected(); /// Return whether the node has any form of "remote" connection via the API or to an MQTT broker bool remote_is_connected(); -/// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode) -#ifdef ARDUINO_ARCH_ESP8266 -void network_setup_mdns(const IPAddress &address, int interface); -#endif -#ifdef ARDUINO_ARCH_ESP32 -void network_setup_mdns(); -#endif - -void network_tick_mdns(); - } // namespace esphome diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 0442e2633b..691f45f91f 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -566,6 +566,10 @@ def add_define(name: str, value: SafeExpType = None): CORE.add_define(Define(name, safe_exp(value))) +def add_platformio_option(key: str, value: Union[str, List[str]]): + CORE.add_platformio_option(key, value) + + async def get_variable(id_: ID) -> "MockObj": """ Wait for the given ID to be defined in the code generation and diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 33fb485c2b..a2eafaa0e8 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,9 +1,6 @@ import logging from esphome.const import ( - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_SETUP_PRIORITY, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, @@ -12,8 +9,8 @@ from esphome.const import ( # pylint: disable=unused-import from esphome.core import coroutine, ID, CORE from esphome.types import ConfigType -from esphome.cpp_generator import RawExpression, add, get_variable -from esphome.cpp_types import App, GPIOPin +from esphome.cpp_generator import add, get_variable +from esphome.cpp_types import App from esphome.util import Registry, RegistryEntry @@ -26,17 +23,13 @@ async def gpio_pin_expression(conf): This is a coroutine, you must await it with a 'await' expression! """ if conf is None: - return + return None from esphome import pins for key, (func, _) in pins.PIN_SCHEMA_REGISTRY.items(): if key in conf: return await coroutine(func)(conf) - - number = conf[CONF_NUMBER] - mode = conf[CONF_MODE] - inverted = conf.get(CONF_INVERTED) - return GPIOPin.new(number, RawExpression(mode), inverted) + return await coroutine(pins.PIN_SCHEMA_REGISTRY[CORE.target_platform][0])(conf) async def register_component(var, config): diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index ee606ec7b3..7a8eb8e04c 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -30,5 +30,7 @@ JsonObject = arduino_json_ns.class_("JsonObject") JsonObjectRef = JsonObject.operator("ref") JsonObjectConstRef = JsonObjectRef.operator("const") Controller = esphome_ns.class_("Controller") - GPIOPin = esphome_ns.class_("GPIOPin") +InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) +gpio_ns = esphome_ns.namespace("gpio") +gpio_Flags = gpio_ns.enum("Flags", is_class=True) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 38689675af..bfe6808729 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -441,16 +441,10 @@ class DashboardEntry: return self.storage.comment @property - def esp_platform(self): + def target_platform(self): if self.storage is None: return None - return self.storage.esp_platform - - @property - def board(self): - if self.storage is None: - return None - return self.storage.board + return self.storage.target_platform @property def update_available(self): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 199c68210e..96dd2fd651 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,13 +4,6 @@ import contextvars from esphome.types import ConfigFragmentType, ID, ConfigPathType import esphome.config_validation as cv -from esphome.const import ( - ARDUINO_VERSION_ESP32, - ARDUINO_VERSION_ESP8266, - CONF_ESPHOME, - CONF_ARDUINO_VERSION, -) -from esphome.core import CORE class FinalValidateConfig(ABC): @@ -63,20 +56,3 @@ def id_declaration_match_schema(schema): return schema(declaration_config) return validator - - -def get_arduino_framework_version(): - path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] - # This is run after core validation, so the property is set even if user didn't - version: str = full_config.get().get_config_for_path(path) - - if CORE.is_esp32: - version_map = ARDUINO_VERSION_ESP32 - elif CORE.is_esp8266: - version_map = ARDUINO_VERSION_ESP8266 - else: - raise ValueError("Platform not supported yet for this validator") - - reverse_map = {v: k for k, v in version_map.items()} - framework_version = reverse_map.get(version) - return framework_version diff --git a/esphome/helpers.py b/esphome/helpers.py index 3420b2cc12..1193d61eaa 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -209,15 +209,21 @@ def write_file(path: Union[Path, str], text: str): raise EsphomeError(f"Could not write file at {path}") from err -def write_file_if_changed(path: Union[Path, str], text: str): +def write_file_if_changed(path: Union[Path, str], text: str) -> bool: + """Write text to the given path, but not if the contents match already. + + Returns true if the file was changed. + """ if not isinstance(path, Path): path = Path(path) src_content = None if path.is_file(): src_content = read_file(path) - if src_content != text: - write_file(path, text) + if src_content == text: + return False + write_file(path, text) + return True def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None: diff --git a/esphome/loader.py b/esphome/loader.py index f74fc6367d..05d2e5a213 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -1,6 +1,5 @@ import logging -import typing -from typing import Callable, List, Optional, Dict, Any, ContextManager +from typing import Callable, List, Optional, Any, ContextManager from types import ModuleType import importlib import importlib.util @@ -8,8 +7,9 @@ import importlib.resources import importlib.abc import sys from pathlib import Path +from dataclasses import dataclass -from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS +from esphome.const import SOURCE_FILE_EXTENSIONS import esphome.core.config from esphome.core import CORE from esphome.types import ConfigType @@ -17,20 +17,13 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) -class SourceFile: - def __init__( - self, - package: importlib.resources.Package, - resource: importlib.resources.Resource, - ) -> None: - self._package = package - self._resource = resource - - def open_binary(self) -> typing.BinaryIO: - return importlib.resources.open_binary(self._package, self._resource) +@dataclass(frozen=True, order=True) +class FileResource: + package: str + resource: str def path(self) -> ContextManager[Path]: - return importlib.resources.path(self._package, self._resource) + return importlib.resources.path(self.package, self.resource) class ComponentManifest: @@ -39,6 +32,13 @@ class ComponentManifest: @property def package(self) -> str: + """Return the package name the module is contained in. + + Examples: + - esphome/components/gpio/__init__.py -> esphome.components.gpio + - esphome/components/gpio/switch/__init__.py -> esphome.components.gpio.switch + - esphome/components/a4988/stepper.py -> esphome.components.a4988 + """ return self.module.__package__ @property @@ -61,10 +61,6 @@ class ComponentManifest: def to_code(self) -> Optional[Callable[[Any], None]]: return getattr(self.module, "to_code", None) - @property - def esp_platforms(self) -> List[str]: - return getattr(self.module, "ESP_PLATFORMS", ESP_PLATFORMS) - @property def dependencies(self) -> List[str]: return getattr(self.module, "DEPENDENCIES", []) @@ -91,17 +87,20 @@ class ComponentManifest: return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) @property - def source_files(self) -> Dict[Path, SourceFile]: - ret = {} + def resources(self) -> List[FileResource]: + """Return a list of all file resources defined in the package of this component. + + This will return all cpp source files that are located in the same folder as the + loaded .py file (does not look through subdirectories) + """ + ret = [] for resource in importlib.resources.contents(self.package): if Path(resource).suffix not in SOURCE_FILE_EXTENSIONS: continue if not importlib.resources.is_resource(self.package, resource): # Not a resource = this is a directory (yeah this is confusing) continue - # Always use / for C++ include names - target_path = Path(*self.package.split(".")) / resource - ret[target_path] = SourceFile(self.package, resource) + ret.append(FileResource(self.package, resource)) return ret diff --git a/esphome/pins.py b/esphome/pins.py index c717424ff3..ae762c1a1a 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,330 +1,143 @@ -import logging +import operator +from functools import reduce -import esphome.config_validation as cv -from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER -from esphome.core import CORE +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) from esphome.util import SimpleRegistry -from esphome import boards - -_LOGGER = logging.getLogger(__name__) - - -def _lookup_pin(value): - if CORE.is_esp8266: - board_pins_dict = boards.ESP8266_BOARD_PINS - base_pins = boards.ESP8266_BASE_PINS - elif CORE.is_esp32: - if CORE.board in boards.ESP32_C3_BOARD_PINS: - board_pins_dict = boards.ESP32_C3_BOARD_PINS - base_pins = boards.ESP32_C3_BASE_PINS - else: - board_pins_dict = boards.ESP32_BOARD_PINS - base_pins = boards.ESP32_BASE_PINS - else: - raise NotImplementedError - - board_pins = board_pins_dict.get(CORE.board, {}) - - # Resolved aliased board pins (shorthand when two boards have the same pin configuration) - while isinstance(board_pins, str): - board_pins = board_pins_dict[board_pins] - - if value in board_pins: - return board_pins[value] - if value in base_pins: - return base_pins[value] - raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {CORE.board}.") - - -def _translate_pin(value): - if isinstance(value, dict) or value is None: - raise cv.Invalid( - "This variable only supports pin numbers, not full pin schemas " - "(with inverted and mode)." - ) - if isinstance(value, int): - return value - try: - return int(value) - except ValueError: - pass - if value.startswith("GPIO"): - return cv.Coerce(int)(value[len("GPIO") :].strip()) - return _lookup_pin(value) - - -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 11: "Flash Command", -} - -_ESP32C3_SDIO_PINS = { - 12: "Flash IO3/HOLD#", - 13: "Flash IO2/WP#", - 14: "Flash CS#", - 15: "Flash CLK", - 16: "Flash IO0/DI", - 17: "Flash IO1/DO", -} - - -def validate_gpio_pin(value): - value = _translate_pin(value) - if CORE.is_esp32_c3: - if value < 0 or value > 22: - raise cv.Invalid(f"ESP32-C3: Invalid pin number: {value}") - if value in _ESP32C3_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP32-C3s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - return value - if CORE.is_esp32: - if value < 0 or value > 39: - raise cv.Invalid(f"ESP32: Invalid pin number: {value}") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "ESP32: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - if value in (20, 24, 28, 29, 30, 31): - # These pins are not exposed in GPIO mux (reason unknown) - # but they're missing from IO_MUX list in datasheet - raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") - return value - if CORE.is_esp8266: - if value < 0 or value > 17: - raise cv.Invalid(f"ESP8266: Invalid pin number: {value}") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP8266s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "ESP8266: Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - return value - raise NotImplementedError - - -def input_pin(value): - value = validate_gpio_pin(value) - if CORE.is_esp8266 and value == 17: - raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.") - return value - - -def input_pullup_pin(value): - value = input_pin(value) - if CORE.is_esp32: - return output_pin(value) - if CORE.is_esp8266: - if value == 0: - raise cv.Invalid( - "GPIO Pin 0 does not support pullup pin mode. " - "Please choose another pin." - ) - return value - raise NotImplementedError - - -def output_pin(value): - value = validate_gpio_pin(value) - if CORE.is_esp32: - if 34 <= value <= 39: - raise cv.Invalid( - f"ESP32: GPIO{value} (34-39) can only be used as an input pin." - ) - return value - if CORE.is_esp8266: - if value == 17: - raise cv.Invalid("GPIO17 (TOUT) is an analog-only pin on the ESP8266.") - return value - raise NotImplementedError - - -def analog_pin(value): - value = validate_gpio_pin(value) - if CORE.is_esp32: - if CORE.is_esp32_c3: - if 0 <= value <= 4: # ADC1 - return value - raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") - if 32 <= value <= 39: # ADC1 - return value - raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") - if CORE.is_esp8266: - if value == 17: # A0 - return value - raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") - raise NotImplementedError - - -input_output_pin = cv.All(input_pin, output_pin) - -PIN_MODES_ESP8266 = [ - "INPUT", - "OUTPUT", - "INPUT_PULLUP", - "OUTPUT_OPEN_DRAIN", - "SPECIAL", - "FUNCTION_1", - "FUNCTION_2", - "FUNCTION_3", - "FUNCTION_4", - "FUNCTION_0", - "WAKEUP_PULLUP", - "WAKEUP_PULLDOWN", - "INPUT_PULLDOWN_16", -] -PIN_MODES_ESP32 = [ - "INPUT", - "OUTPUT", - "INPUT_PULLUP", - "OUTPUT_OPEN_DRAIN", - "SPECIAL", - "FUNCTION_1", - "FUNCTION_2", - "FUNCTION_3", - "FUNCTION_4", - "PULLUP", - "PULLDOWN", - "INPUT_PULLDOWN", - "OPEN_DRAIN", - "FUNCTION_5", - "FUNCTION_6", - "ANALOG", -] - - -def pin_mode(value): - if CORE.is_esp32: - return cv.one_of(*PIN_MODES_ESP32, upper=True)(value) - if CORE.is_esp8266: - return cv.one_of(*PIN_MODES_ESP8266, upper=True)(value) - raise NotImplementedError - - -GPIO_FULL_OUTPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): output_pin, - cv.Optional(CONF_MODE, default="OUTPUT"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_INPUT_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default="INPUT"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): input_pin, - cv.Optional(CONF_MODE, default="INPUT_PULLUP"): pin_mode, - cv.Optional(CONF_INVERTED, default=False): cv.boolean, - } -) - -GPIO_FULL_ANALOG_PIN_SCHEMA = cv.Schema( - { - cv.Required(CONF_NUMBER): analog_pin, - cv.Optional(CONF_MODE, default="INPUT"): pin_mode, - } -) - - -def shorthand_output_pin(value): - value = output_pin(value) - return GPIO_FULL_OUTPUT_PIN_SCHEMA({CONF_NUMBER: value}) - - -def shorthand_input_pin(value): - value = input_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value}) - - -def shorthand_input_pullup_pin(value): - value = input_pullup_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA( - { - CONF_NUMBER: value, - CONF_MODE: "INPUT_PULLUP", - } - ) - - -def shorthand_analog_pin(value): - value = analog_pin(value) - return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value}) - - -def validate_has_interrupt(value): - if CORE.is_esp8266: - if value[CONF_NUMBER] >= 16: - raise cv.Invalid( - f"Pins GPIO16 and GPIO17 do not support interrupts and cannot be used here, got {value[CONF_NUMBER]}" - ) - return value +from esphome.core import CORE PIN_SCHEMA_REGISTRY = SimpleRegistry() -def internal_gpio_output_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_OUTPUT_PIN_SCHEMA(value) - return shorthand_output_pin(value) +def _set_mode(value, default_mode): + import esphome.config_validation as cv + + if CONF_MODE not in value: + return {**value, CONF_MODE: default_mode} + mode = value[CONF_MODE] + if not isinstance(mode, str): + return value + # mode is a string, try parsing it like arduino pin modes + PIN_MODES = { + "INPUT": { + CONF_INPUT: True, + }, + "OUTPUT": { + CONF_OUTPUT: True, + }, + "INPUT_PULLUP": { + CONF_INPUT: True, + CONF_PULLUP: True, + }, + "OUTPUT_OPEN_DRAIN": { + CONF_OUTPUT: True, + CONF_OPEN_DRAIN: True, + }, + "INPUT_PULLDOWN_16": { + CONF_INPUT: True, + CONF_PULLDOWN: True, + }, + "INPUT_PULLDOWN": { + CONF_INPUT: True, + CONF_PULLDOWN: True, + }, + } + if mode.upper() not in PIN_MODES: + raise cv.Invalid(f"Unknown pin mode {mode}", [CONF_MODE]) + return {**value, CONF_MODE: PIN_MODES[mode.upper()]} -def gpio_output_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][0](value) - return internal_gpio_output_pin_schema(value) +def _schema_creator(default_mode, internal: bool = False): + def validator(value): + if not isinstance(value, dict): + return validator({CONF_NUMBER: value}) + value = _set_mode(value, default_mode) + if not internal: + for key, entry in PIN_SCHEMA_REGISTRY.items(): + if key != CORE.target_platform and key in value: + return entry[1](value) + return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value) + + return validator -def internal_gpio_input_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_INPUT_PIN_SCHEMA(value) - return shorthand_input_pin(value) +def _internal_number_creator(mode): + def validator(value): + value_d = {CONF_NUMBER: value} + value_d = _set_mode(value_d, mode) + return PIN_SCHEMA_REGISTRY[CORE.target_platform][1](value_d)[CONF_NUMBER] + + return validator -def internal_gpio_analog_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_ANALOG_PIN_SCHEMA(value) - return shorthand_analog_pin(value) +def gpio_flags_expr(mode): + """Convert the given mode dict to a gpio Flags expression""" + import esphome.codegen as cg + + FLAGS_MAPPING = { + CONF_INPUT: cg.gpio_Flags.FLAG_INPUT, + CONF_OUTPUT: cg.gpio_Flags.FLAG_OUTPUT, + CONF_OPEN_DRAIN: cg.gpio_Flags.FLAG_OPEN_DRAIN, + CONF_PULLUP: cg.gpio_Flags.FLAG_PULLUP, + CONF_PULLDOWN: cg.gpio_Flags.FLAG_PULLDOWN, + } + active_flags = [v for k, v in FLAGS_MAPPING.items() if mode.get(k)] + if active_flags: + return cg.gpio_Flags.FLAG_NONE + + return reduce(operator.or_, active_flags) -def gpio_input_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][1](value) - return internal_gpio_input_pin_schema(value) - - -def internal_gpio_input_pullup_pin_schema(value): - if isinstance(value, dict): - return GPIO_FULL_INPUT_PULLUP_PIN_SCHEMA(value) - return shorthand_input_pullup_pin(value) - - -def gpio_input_pullup_pin_schema(value): - if isinstance(value, dict): - for key, entry in PIN_SCHEMA_REGISTRY.items(): - if key in value: - return entry[1][1](value) - return internal_gpio_input_pullup_pin_schema(value) +gpio_pin_schema = _schema_creator +internal_gpio_pin_number = _internal_number_creator +gpio_output_pin_schema = _schema_creator( + { + CONF_OUTPUT: True, + } +) +gpio_input_pin_schema = _schema_creator( + { + CONF_INPUT: True, + } +) +gpio_input_pullup_pin_schema = _schema_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + } +) +internal_gpio_output_pin_schema = _schema_creator( + { + CONF_OUTPUT: True, + }, + internal=True, +) +internal_gpio_output_pin_number = _internal_number_creator({CONF_OUTPUT: True}) +internal_gpio_input_pin_schema = _schema_creator( + { + CONF_INPUT: True, + }, + internal=True, +) +internal_gpio_input_pin_number = _internal_number_creator({CONF_INPUT: True}) +internal_gpio_input_pullup_pin_schema = _schema_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + }, + internal=True, +) +internal_gpio_input_pullup_pin_number = _internal_number_creator( + { + CONF_INPUT: True, + CONF_PULLUP: True, + } +) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index a99081a650..100def3310 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -196,7 +196,7 @@ def _parse_register(config, regex, line): STACKTRACE_ESP8266_EXCEPTION_TYPE_RE = re.compile(r"[eE]xception \((\d+)\):") STACKTRACE_ESP8266_PC_RE = re.compile(r"epc1=0x(4[0-9a-fA-F]{7})") STACKTRACE_ESP8266_EXCVADDR_RE = re.compile(r"excvaddr=0x(4[0-9a-fA-F]{7})") -STACKTRACE_ESP32_PC_RE = re.compile(r"PC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") +STACKTRACE_ESP32_PC_RE = re.compile(r".*PC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7}).*") STACKTRACE_ESP32_EXCVADDR_RE = re.compile(r"EXCVADDR\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") STACKTRACE_ESP32_C3_PC_RE = re.compile(r"MEPC\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") STACKTRACE_ESP32_C3_RA_RE = re.compile(r"RA\s*:\s*(?:0x)?(4[0-9a-fA-F]{7})") @@ -204,7 +204,7 @@ STACKTRACE_BAD_ALLOC_RE = re.compile( r"^last failed alloc call: (4[0-9a-fA-F]{7})\((\d+)\)$" ) STACKTRACE_ESP32_BACKTRACE_RE = re.compile( - r"Backtrace:(?:\s+0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+" + r"Backtrace:(?:\s*0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8})+" ) STACKTRACE_ESP32_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") STACKTRACE_ESP8266_BACKTRACE_PC_RE = re.compile(r"4[0-9a-f]{7}") diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 517bb508ba..e70d3d6c93 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -40,10 +40,8 @@ class StorageJSON: comment, esphome_version, src_version, - arduino_version, address, - esp_platform, - board, + target_platform, build_path, firmware_bin_path, loaded_integrations, @@ -60,15 +58,10 @@ class StorageJSON: # The version of the file in src/main.cpp - Used to migrate the file assert src_version is None or isinstance(src_version, int) self.src_version = src_version # type: int - # The version of the Arduino framework, the build files need to be cleared each time - # this changes - self.arduino_version = arduino_version # type: str # Address of the ESP, for example livingroom.local or a static IP self.address = address # type: str # The type of ESP in use, either ESP32 or ESP8266 - self.esp_platform = esp_platform # type: str - # The ESP board used, for example nodemcuv2 - self.board = board # type: str + self.target_platform = target_platform # type: str # The absolute path to the platformio project self.build_path = build_path # type: str # The absolute path to the firmware binary @@ -84,10 +77,8 @@ class StorageJSON: "comment": self.comment, "esphome_version": self.esphome_version, "src_version": self.src_version, - "arduino_version": self.arduino_version, "address": self.address, - "esp_platform": self.esp_platform, - "board": self.board, + "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, "loaded_integrations": self.loaded_integrations, @@ -109,28 +100,24 @@ class StorageJSON: comment=esph.comment, esphome_version=const.__version__, src_version=1, - arduino_version=esph.arduino_version, address=esph.address, - esp_platform=esph.esp_platform, - board=esph.board, + target_platform=esph.target_platform, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, loaded_integrations=list(esph.loaded_integrations), ) @staticmethod - def from_wizard(name, address, esp_platform, board): - # type: (str, str, str, str) -> StorageJSON + def from_wizard(name, address, esp_platform): + # type: (str, str, str) -> StorageJSON return StorageJSON( storage_version=1, name=name, comment=None, esphome_version=const.__version__, src_version=1, - arduino_version=None, address=address, - esp_platform=esp_platform, - board=board, + target_platform=esp_platform, build_path=None, firmware_bin_path=None, loaded_integrations=[], @@ -147,10 +134,8 @@ class StorageJSON: "esphome_version", storage.get("esphomeyaml_version") ) src_version = storage.get("src_version") - arduino_version = storage.get("arduino_version") address = storage.get("address") esp_platform = storage.get("esp_platform") - board = storage.get("board") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") loaded_integrations = storage.get("loaded_integrations", []) @@ -160,10 +145,8 @@ class StorageJSON: comment, esphome_version, src_version, - arduino_version, address, esp_platform, - board, build_path, firmware_bin_path, loaded_integrations, diff --git a/esphome/wizard.py b/esphome/wizard.py index abb17e3119..5c35fac73a 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -10,7 +10,6 @@ from esphome.helpers import get_bool_env, write_file from esphome.log import color, Fore # pylint: disable=anomalous-backslash-in-string -from esphome.boards import ESP32_BOARD_PINS, ESP8266_BOARD_PINS from esphome.storage_json import StorageJSON, ext_storage_path from esphome.util import safe_print from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD @@ -116,6 +115,8 @@ captive_portal: def wizard_write(path, **kwargs): + from esphome.components.esp8266 import boards as esp8266_boards + name = kwargs["name"] board = kwargs["board"] @@ -124,11 +125,13 @@ def wizard_write(path, **kwargs): kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: - kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" + kwargs["platform"] = ( + "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" + ) platform = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, f"{name}.local", platform, board) + storage = StorageJSON.from_wizard(name, f"{name}.local", platform) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) @@ -168,6 +171,9 @@ def strip_accents(value): def wizard(path): + from esphome.components.esp32 import boards as esp32_boards + from esphome.components.esp8266 import boards as esp8266_boards + if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( f"Please make your configuration file {color(Fore.CYAN, path)} have the extension .yaml or .yml" @@ -266,10 +272,10 @@ def wizard(path): # Don't sleep because user needs to copy link if platform == "ESP32": safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") - boards = list(ESP32_BOARD_PINS.keys()) + boards = list(esp32_boards.ESP32_BOARD_PINS.keys()) else: safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") - boards = list(ESP8266_BOARD_PINS.keys()) + boards = list(esp8266_boards.ESP8266_BOARD_PINS.keys()) safe_print(f"Options: {', '.join(sorted(boards))}") while True: diff --git a/esphome/writer.py b/esphome/writer.py index 2e1e19de54..29532d4f64 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -2,17 +2,13 @@ import logging import os import re from pathlib import Path -from typing import Dict +from typing import Dict, List, Union from esphome.config import iter_components from esphome.const import ( - CONF_BOARD_FLASH_MODE, - CONF_ESPHOME, - CONF_PLATFORMIO_OPTIONS, HEADER_FILE_EXTENSIONS, SOURCE_FILE_EXTENSIONS, __version__, - ARDUINO_VERSION_ESP8266, ENV_NOGITIGNORE, ) from esphome.core import CORE, EsphomeError @@ -25,7 +21,6 @@ from esphome.helpers import ( get_bool_env, ) from esphome.storage_json import StorageJSON, storage_path -from esphome.boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS from esphome import loader _LOGGER = logging.getLogger(__name__) @@ -72,7 +67,9 @@ upload_flags = """, ) -UPLOAD_SPEED_OVERRIDE = {"esp210": 57600} +UPLOAD_SPEED_OVERRIDE = { + "esp210": 57600, +} def get_flags(key): @@ -166,10 +163,6 @@ def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool if old.src_version != new.src_version: return True - if old.arduino_version != new.arduino_version: - return True - if old.board != new.board: - return True if old.build_path != new.build_path: return True return False @@ -192,10 +185,10 @@ def update_storage_json(): new.save(path) -def format_ini(data): +def format_ini(data: Dict[str, Union[str, List[str]]]) -> str: content = "" for key, value in sorted(data.items()): - if isinstance(value, (list, set, tuple)): + if isinstance(value, list): content += f"{key} =\n" for x in value: content += f" {x}\n" @@ -204,82 +197,15 @@ def format_ini(data): return content -def gather_lib_deps(): - return [x.as_lib_dep for x in CORE.libraries] - - -def gather_build_flags(overrides): - build_flags = list(CORE.build_flags) - build_flags += [overrides] if isinstance(overrides, str) else overrides - - # avoid changing build flags order - return list(sorted(build_flags)) - - -ESP32_LARGE_PARTITIONS_CSV = """\ -nvs, data, nvs, 0x009000, 0x005000, -otadata, data, ota, 0x00e000, 0x002000, -app0, app, ota_0, 0x010000, 0x1C0000, -app1, app, ota_1, 0x1D0000, 0x1C0000, -eeprom, data, 0x99, 0x390000, 0x001000, -spiffs, data, spiffs, 0x391000, 0x00F000 -""" - - def get_ini_content(): - overrides = CORE.config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS, {}) - - lib_deps = gather_lib_deps() - build_flags = gather_build_flags(overrides.pop("build_flags", [])) - - data = { - "platform": CORE.arduino_version, - "board": CORE.board, - "framework": "arduino", - "lib_deps": lib_deps + ["${common.lib_deps}"], - "build_flags": build_flags + ["${common.build_flags}"], - "upload_speed": UPLOAD_SPEED_OVERRIDE.get(CORE.board, 115200), - } - - if CORE.is_esp32: - data["board_build.partitions"] = "partitions.csv" - partitions_csv = CORE.relative_build_path("partitions.csv") - write_file_if_changed(partitions_csv, ESP32_LARGE_PARTITIONS_CSV) - - # pylint: disable=unsubscriptable-object - if CONF_BOARD_FLASH_MODE in CORE.config[CONF_ESPHOME]: - flash_mode = CORE.config[CONF_ESPHOME][CONF_BOARD_FLASH_MODE] - data["board_build.flash_mode"] = flash_mode - - # Build flags - if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: - flash_size = ESP8266_FLASH_SIZES[CORE.board] - ld_scripts = ESP8266_LD_SCRIPTS[flash_size] - - versions_with_old_ldscripts = [ - ARDUINO_VERSION_ESP8266["2.4.0"], - ARDUINO_VERSION_ESP8266["2.4.1"], - ARDUINO_VERSION_ESP8266["2.4.2"], - ] - if CORE.arduino_version == ARDUINO_VERSION_ESP8266["2.3.0"]: - # No ld script support - ld_script = None - if CORE.arduino_version in versions_with_old_ldscripts: - # Old ld script path - ld_script = ld_scripts[0] - else: - ld_script = ld_scripts[1] - - if ld_script is not None: - data["board_build.ldscript"] = ld_script - - # Ignore libraries that are not explicitly used, but may - # be added by LDF - # data['lib_ldf_mode'] = 'chain' - data.update(overrides) + CORE.add_platformio_option( + "lib_deps", [x.as_lib_dep for x in CORE.libraries] + ["${common.lib_deps}"] + ) + # Sort to avoid changing build flags order + CORE.add_platformio_option("build_flags", sorted(CORE.build_flags)) content = f"[env:{CORE.name}]\n" - content += format_ini(data) + content += format_ini(CORE.platformio_options) return content @@ -361,12 +287,15 @@ or use the custom_components folder. def copy_src_tree(): - source_files: Dict[Path, loader.SourceFile] = {} + source_files: List[loader.FileResource] = [] for _, component, _ in iter_components(CORE.config): - source_files.update(component.source_files) + source_files += component.resources + source_files_map = { + Path(x.package.replace(".", "/") + "/" + x.resource): x for x in source_files + } # Convert to list and sort - source_files_l = list(source_files.items()) + source_files_l = list(source_files_map.items()) source_files_l.sort() # Build #include list for esphome.h @@ -377,7 +306,7 @@ def copy_src_tree(): include_l.append("") include_s = "\n".join(include_l) - source_files_copy = source_files.copy() + source_files_copy = source_files_map.copy() ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] for t in ignore_targets: source_files_copy.pop(t) @@ -420,6 +349,11 @@ def copy_src_tree(): CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) + if CORE.is_esp32: + from esphome.components.esp32 import copy_files + + copy_files() + def generate_defines_h(): define_content_l = [x.as_macro for x in CORE.defines] diff --git a/platformio.ini b/platformio.ini index 89e9ce9374..0e2649e63a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,7 +5,7 @@ [platformio] default_envs = esp8266, esp32 -src_dir = . +src_dir = esphome include_dir = [runtime] @@ -47,7 +47,11 @@ src_filter = [common:esp8266] extends = common -platform = platformio/espressif8266@3.1.0 +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif8266 @ 3.2.0 +platform_packages = + platformio/framework-arduinoespressif8266 @ ~3.30002.0 + framework = arduino board = nodemcuv2 lib_deps = @@ -55,29 +59,88 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp +build_flags = + ${common.build_flags} + -DUSE_ESP8266 [common:esp32] extends = common -platform = platformio/espressif32@3.2.0 +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif32 @ 3.3.2 +platform_packages = + platformio/framework-arduinoespressif32 @ ~3.10006.0 + framework = arduino board = nodemcu-32s lib_deps = ${common.lib_deps} Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp +build_flags = + ${common.build_flags} + -DUSE_ESP32 +src_filter = ${common.src_filter} + +[common:espidf] +; when changing this also copy it to esphome-docker-base images +platform = platformio/espressif32 @ 3.3.2 +platform_packages = + platformio/framework-espidf @ ~3.40300.0 + +framework = espidf +board = nodemcu-32s +lib_deps = + esphome/noise-c@0.1.1 ; used by api + espressif/esp32-camera@1.0.0 ; used by esp32_camera + NeoPixelBus@2.6.7 +build_flags = + ${common.build_flags} + ${common:esp32.build_flags} + -DUSE_ESP_IDF + -DUSE_ESP32_FRAMEWORK_ESP_IDF +src_filter = ${common:esp32.src_filter} [env:esp8266] extends = common:esp8266 -build_flags = ${common:esp8266.build_flags} ${runtime.build_flags} +build_flags = + ${common:esp8266.build_flags} + ${runtime.build_flags} + -DUSE_ARDUINO + -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp8266-tidy] extends = common:esp8266 -build_flags = ${common:esp8266.build_flags} ${clangtidy.build_flags} +build_flags = + ${common:esp8266.build_flags} + ${clangtidy.build_flags} + -DUSE_ARDUINO + -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp32] extends = common:esp32 -build_flags = ${common:esp32.build_flags} ${runtime.build_flags} +build_flags = + ${common:esp32.build_flags} + ${runtime.build_flags} + -DUSE_ARDUINO + -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-tidy] extends = common:esp32 -build_flags = ${common:esp32.build_flags} ${clangtidy.build_flags} +build_flags = + ${common:esp32.build_flags} + ${clangtidy.build_flags} + -DUSE_ARDUINO + -DUSE_ESP32_FRAMEWORK_ARDUINO + +[env:esp32-idf] +extends = common:espidf +build_flags = + ${common:espidf.build_flags} + ${runtime.build_flags} + + +[env:esp32-idf-tidy] +extends = common:espidf +build_flags = + ${common:espidf.build_flags} + ${clangtidy.build_flags} diff --git a/requirements.txt b/requirements.txt index 64ffd366e1..c6d967fc33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,7 @@ esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 aioesphomeapi==9.1.0 + +# esp-idf requires this, but doesn't bundle it by default +# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 +kconfiglib==13.7.1 diff --git a/script/ci-custom.py b/script/ci-custom.py index 3191842d8c..8e9ca487a6 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -408,7 +408,6 @@ ARDUINO_FORBIDDEN_RE = r"[^\w\d](" + r"|".join(ARDUINO_FORBIDDEN) + r")\(.*" exclude=[ "esphome/components/mqtt/custom_mqtt_device.h", "esphome/components/sun/sun.cpp", - "esphome/core/esphal.*", ], ) def lint_no_arduino_framework_functions(fname, match): @@ -422,6 +421,28 @@ def lint_no_arduino_framework_functions(fname, match): ) +IDF_CONVERSION_FORBIDDEN = { + "ARDUINO_ARCH_ESP32": "USE_ESP32", + "ARDUINO_ARCH_ESP8266": "USE_ESP8266", + "pgm_read_byte": "progmem_read_byte", + "ICACHE_RAM_ATTR": "IRAM_ATTR", + "esphome/core/esphal.h": "esphome/core/hal.h", +} +IDF_CONVERSION_FORBIDDEN_RE = r"(" + r"|".join(IDF_CONVERSION_FORBIDDEN) + r").*" + + +@lint_re_check( + IDF_CONVERSION_FORBIDDEN_RE, + include=cpp_include, +) +def lint_no_removed_in_idf_conversions(fname, match): + replacement = IDF_CONVERSION_FORBIDDEN[match.group(1)] + return ( + f"The macro {highlight(match.group(1))} can no longer be used in ESPHome directly. " + f"Plese use {highlight(replacement)} instead." + ) + + @lint_re_check( r"[^\w\d]byte\s+[\w\d]+\s*=", include=cpp_include, @@ -498,6 +519,8 @@ def lint_relative_py_import(fname): ], exclude=[ "esphome/components/socket/headers.h", + "esphome/components/esp32/core.cpp", + "esphome/components/esp8266/core.cpp", ], ) def lint_namespace(fname, content): @@ -575,7 +598,7 @@ def lint_inclusive_language(fname, match): "esphome/components/text_sensor/text_sensor.h", "esphome/components/climate/climate.h", "esphome/core/component.h", - "esphome/core/esphal.h", + "esphome/core/gpio.h", "esphome/core/log.h", "tests/custom.h", ], diff --git a/script/clang-tidy b/script/clang-tidy index 463959fa5d..8a1baf8a22 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -45,7 +45,11 @@ def clang_options(idedata): # pretend we're an Xtensa compiler, which gates some features in the headers '-D__XTENSA__', # allow to condition code on the presence of clang-tidy - '-DCLANG_TIDY' + '-DCLANG_TIDY', + # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know + '-D__XTENSA_API_H__', + # (esp-idf) Fix __once_callable in some libstdc++ headers + '-D_GLIBCXX_HAVE_TLS', ] # copy compiler flags, except those clang doesn't understand. @@ -57,7 +61,8 @@ def clang_options(idedata): # add include directories, using -isystem for dependencies to suppress their errors for directory in idedata['includes']['toolchain']: - cmd.extend(['-isystem', directory]) + if 'xtensa-esp32s2-elf' not in directory: + cmd.extend(['-isystem', directory]) for directory in sorted(set(idedata['includes']['build'])): dependency = "framework-arduino" in directory or "/libdeps/" in directory cmd.extend(['-isystem' if dependency else '-I', directory]) diff --git a/script/helpers.py b/script/helpers.py index d32e0eafbe..2c40d47e04 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -112,15 +112,36 @@ def git_ls_files(patterns=None): return {s[3].strip(): int(s[0]) for s in lines} +IDF_TIDY_SDKCONFIG = """\ +CONFIG_BT_ENABLED=y +""" + + def load_idedata(environment): platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" + changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): changed = True elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: changed = True - else: - changed = False + + if environment == "esp32-idf-tidy": + # sdkconfig needs to be written before idedata is run + # but the file is also modified by the build process, so + # store a temp file to keep track of the + + sdk_internal = Path(temp_folder) / f"{environment}-internal-sdkconfig" + sdkconfig = Path(root_path) / f"sdkconfig.{environment}" + if ( + changed + or not sdk_internal.is_file() + or sdk_internal.read_text() != IDF_TIDY_SDKCONFIG + ): + changed = True + sdkconfig.write_text(IDF_TIDY_SDKCONFIG) + sdk_internal.parent.mkdir(exist_ok=True) + sdk_internal.write_text(IDF_TIDY_SDKCONFIG) if not changed: return json.loads(temp_idedata.read_text()) diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 8da93a476e..514bc6ee5f 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -30,7 +30,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): # Then assert 'bs_1->set_name("test bs1");' in main_cpp - assert "bs_1->set_pin(new GPIOPin" in main_cpp + assert "bs_1->set_pin(" in main_cpp def test_binary_sensor_config_value_internal_set(generate_main): diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index f9ba868096..d956387665 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -27,12 +27,6 @@ void setup() { auto *ota = new ota::OTAComponent(); // NOLINT ota->set_port(8266); - auto *gpio = new gpio::GPIOSwitch(); // NOLINT - gpio->set_name("GPIO Switch"); - gpio->set_pin(new GPIOPin(8, OUTPUT, false)); // NOLINT - App.register_component(gpio); - App.register_switch(gpio); - App.setup(); } diff --git a/tests/test1.yaml b/tests/test1.yaml index cd4179f394..ab089dbc15 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -71,7 +71,6 @@ wifi: password: '' channel: 14 bssid: 'A1:63:95:47:D3:1D' - enable_mdns: true manual_ip: static_ip: 192.168.178.230 gateway: 192.168.178.1 @@ -82,6 +81,9 @@ wifi: reboot_timeout: 120s power_save_mode: light +mdns: + disabled: false + http_request: useragent: esphome/device timeout: 10s @@ -170,6 +172,7 @@ i2c: scan: True frequency: 100kHz setup_priority: -100 + id: i2c_bus spi: clk_pin: GPIO21 @@ -177,15 +180,18 @@ spi: miso_pin: GPIO23 uart: - - tx_pin: GPIO22 - rx_pin: GPIO23 + - tx_pin: + number: GPIO22 + inverted: yes + rx_pin: + number: GPIO23 + inverted: yes baud_rate: 115200 id: uart0 parity: NONE data_bits: 8 stop_bits: 1 rx_buffer_size: 512 - invert: false - id: adalight_uart tx_pin: GPIO25 @@ -246,6 +252,7 @@ deep_sleep: ads1115: address: 0x48 + i2c_id: i2c_bus dallas: pin: GPIO23 @@ -436,6 +443,7 @@ sensor: availability: state_topic: livingroom/custom_state_topic measurement_duration: 31 + i2c_id: i2c_bus - platform: bme280 temperature: name: 'Outside Temperature' @@ -449,6 +457,7 @@ sensor: address: 0x77 iir_filter: 16x update_interval: 15s + i2c_id: i2c_bus - platform: bme680 temperature: name: 'Outside Temperature' @@ -464,6 +473,7 @@ sensor: temperature: 320 duration: 150ms update_interval: 15s + i2c_id: i2c_bus - platform: bmp085 temperature: name: 'Outside Temperature' @@ -473,6 +483,7 @@ sensor: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); update_interval: 15s + i2c_id: i2c_bus - platform: bmp280 temperature: name: 'Outside Temperature' @@ -482,6 +493,7 @@ sensor: address: 0x77 update_interval: 15s iir_filter: 16x + i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 name: 'Living Room Temperature' @@ -503,6 +515,7 @@ sensor: humidity: name: 'Living Room Humidity 4' update_interval: 15s + i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor @@ -515,6 +528,7 @@ sensor: humidity: name: 'Living Room Pressure 5' update_interval: 15s + i2c_id: i2c_bus - platform: hlw8012 sel_pin: 5 cf_pin: 14 @@ -560,6 +574,7 @@ sensor: range: 130uT oversampling: 8x update_interval: 15s + i2c_id: i2c_bus - platform: qmc5883l address: 0x0D field_strength_x: @@ -573,6 +588,7 @@ sensor: range: 800uT oversampling: 256x update_interval: 15s + i2c_id: i2c_bus - platform: hx711 name: 'HX711 Value' dout_pin: GPIO23 @@ -593,6 +609,7 @@ sensor: max_voltage: 32.0V max_current: 3.2A update_interval: 15s + i2c_id: i2c_bus - platform: ina226 address: 0x40 shunt_resistance: 0.1 ohm @@ -606,6 +623,7 @@ sensor: name: 'INA226 Shunt Voltage' max_current: 3.2A update_interval: 15s + i2c_id: i2c_bus - platform: ina3221 address: 0x40 channel_1: @@ -619,12 +637,14 @@ sensor: shunt_voltage: name: 'INA3221 Channel 1 Shunt Voltage' update_interval: 15s + i2c_id: i2c_bus - platform: htu21d temperature: name: 'Living Room Temperature 6' humidity: name: 'Living Room Humidity 6' update_interval: 15s + i2c_id: i2c_bus - platform: max6675 name: 'Living Room Temperature' cs_pin: GPIO23 @@ -670,6 +690,7 @@ sensor: name: 'MPU6050 Gyro z' temperature: name: 'MPU6050 Temperature' + i2c_id: i2c_bus - platform: ms5611 temperature: name: 'Outside Temperature' @@ -677,6 +698,7 @@ sensor: name: 'Outside Pressure' address: 0x77 update_interval: 15s + i2c_id: i2c_bus - platform: pmsa003i pm_1_0: name: "PMSA003i PM1.0" @@ -698,6 +720,7 @@ sensor: name: "PMSA003i PMC <10µm" address: 0x12 standard_units: True + i2c_id: i2c_bus - platform: pulse_counter name: 'Pulse Counter' pin: GPIO12 @@ -768,10 +791,12 @@ sensor: humidity: name: 'Living Room Humidity 8' address: 0x44 + i2c_id: i2c_bus update_interval: 15s - platform: sts3x name: 'Living Room Temperature 9' address: 0x4A + i2c_id: i2c_bus - platform: scd30 co2: name: 'Living Room CO2 9' @@ -785,6 +810,7 @@ sensor: altitude_compensation: 10m ambient_pressure_compensation: 961mBar temperature_offset: 4.2C + i2c_id: i2c_bus - platform: sgp30 eco2: name: 'Workshop eCO2' @@ -794,6 +820,7 @@ sensor: accuracy_decimals: 1 address: 0x58 update_interval: 5s + i2c_id: i2c_bus - platform: sps30 pm_1_0: name: 'Workshop PM <1µm Weight concentration' @@ -824,6 +851,7 @@ sensor: id: 'workshop_PMC_10_0' address: 0x69 update_interval: 10s + i2c_id: i2c_bus - platform: sht4x temperature: name: 'SHT4X Temperature' @@ -831,6 +859,7 @@ sensor: name: 'SHT4X Humidity' address: 0x44 update_interval: 15s + i2c_id: i2c_bus - platform: shtcx temperature: name: 'Living Room Temperature 10' @@ -838,6 +867,7 @@ sensor: name: 'Living Room Humidity 10' address: 0x70 update_interval: 15s + i2c_id: i2c_bus - platform: template name: 'Template Sensor' state_class: measurement @@ -863,6 +893,7 @@ sensor: is_cs_package: true integration_time: 402ms gain: 16x + i2c_id: i2c_bus - platform: tsl2591 id: this_little_light_of_mine address: 0x29 @@ -882,6 +913,7 @@ sensor: calculated_lux: name: "tsl2591 calculated_lux" id: tsl2591_cl + i2c_id: i2c_bus - platform: ultrasonic trigger_pin: GPIO25 echo_pin: @@ -921,6 +953,7 @@ sensor: name: CCS811 TVOC update_interval: 30s baseline: 0x4242 + i2c_id: i2c_bus - platform: tx20 wind_speed: name: 'Windspeed' @@ -946,6 +979,7 @@ sensor: - platform: tmp117 name: 'TMP117 Temperature' update_interval: 5s + i2c_id: i2c_bus - platform: hm3301 pm_1_0: name: 'PM1.0' @@ -956,6 +990,7 @@ sensor: aqi: name: 'AQI' calculation_type: 'CAQI' + i2c_id: i2c_bus - platform: teleinfo tag_name: "HCHC" name: "hchc" @@ -965,15 +1000,18 @@ sensor: - platform: mcp9808 name: 'MCP9808 Temperature' update_interval: 15s + i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 unit_of_measurement: 'pH' + i2c_id: i2c_bus - platform: sdp3x name: "HVAC Filter Pressure drop" id: filter_pressure update_interval: 5s accuracy_decimals: 3 + i2c_id: i2c_bus - platform: cs5460a id: cs5460a1 current: @@ -1216,14 +1254,18 @@ binary_sensor: pca9685: frequency: 500 address: 0x0 + i2c_id: i2c_bus tlc59208f: - address: 0x20 id: tlc59208f_1 + i2c_id: i2c_bus - address: 0x22 id: tlc59208f_2 + i2c_id: i2c_bus - address: 0x24 id: tlc59208f_3 + i2c_id: i2c_bus my9231: data_pin: GPIO12 @@ -1363,6 +1405,7 @@ output: id: dac_output - platform: mcp4725 id: mcp4725_dac_output + i2c_id: i2c_bus e131: @@ -1992,6 +2035,7 @@ display: address: 0x3F lambda: |- it.print("Hello World!"); + i2c_id: i2c_bus - platform: max7219 cs_pin: GPIO23 num_chips: 1 @@ -2039,6 +2083,7 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + i2c_id: i2c_bus - platform: ssd1306_spi model: 'SSD1306 128x64' cs_pin: GPIO23 @@ -2073,6 +2118,7 @@ display: - id: page13272 lambda: |- // Nothing + i2c_id: i2c_bus - platform: ssd1327_spi model: 'SSD1327 128x128' cs_pin: GPIO23 @@ -2151,6 +2197,7 @@ pn532_spi: payload: !lambda 'return x;' pn532_i2c: + i2c_id: i2c_bus rdm6300: uart_id: uart0 @@ -2167,11 +2214,13 @@ rc522_i2c: on_tag: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); + i2c_id: i2c_bus - update_interval: 1s on_tag: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); + i2c_id: i2c_bus gps: uart_id: uart0 @@ -2198,6 +2247,7 @@ time: on_time: seconds: 0 then: ds1307.read_time + i2c_id: i2c_bus cover: - platform: template @@ -2225,31 +2275,35 @@ debug: tca9548a: - address: 0x70 id: multiplex0 - scan: True + channels: + - bus_id: multiplex0_chan0 + channel: 0 + i2c_id: i2c_bus - address: 0x71 id: multiplex1 - scan: True - multiplexer: - id: multiplex0 - channel: 0 + i2c_id: multiplex0_chan0 pcf8574: - id: 'pcf8574_hub' address: 0x21 pcf8575: False + i2c_id: i2c_bus mcp23017: - id: 'mcp23017_hub' open_drain_interrupt: 'true' + i2c_id: i2c_bus mcp23008: - id: 'mcp23008_hub' address: 0x22 open_drain_interrupt: 'true' + i2c_id: i2c_bus mcp23016: - id: 'mcp23016_hub' address: 0x23 + i2c_id: i2c_bus stepper: - platform: a4988 diff --git a/tests/test2.yaml b/tests/test2.yaml index 54932953d5..e6df4d513e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -14,13 +14,15 @@ ethernet: clk_mode: GPIO0_IN phy_addr: 0 power_pin: GPIO25 - enable_mdns: false manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 subnet: 255.255.255.0 domain: .local +mdns: + disabled: true + api: i2c: @@ -360,7 +362,7 @@ esp32_ble_tracker: ble_client: - mac_address: 01:02:03:04:05:06 id: airthings01 - + airthings_ble: #esp32_ble_beacon: diff --git a/tests/test3.yaml b/tests/test3.yaml index 5602481c36..de46cec99c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -259,7 +259,7 @@ ota: logger: hardware_uart: UART1 level: DEBUG - esp8266_store_log_strings_in_flash: false + esp8266_store_log_strings_in_flash: true web_server: diff --git a/tests/test4.yaml b/tests/test4.yaml index e52e8cc33c..a205f0802e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -379,7 +379,7 @@ display: it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red); rotation: 0° update_interval: 16ms - + - platform: waveshare_epaper cs_pin: GPIO23 dc_pin: GPIO23 @@ -407,6 +407,23 @@ display: full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: inkplate6 + id: inkplate_display + greyscale: false + partial_updating: false + update_interval: 60s + + ckv_pin: GPIO1 + sph_pin: GPIO1 + gmod_pin: GPIO1 + gpio0_enable_pin: GPIO1 + oe_pin: GPIO1 + spv_pin: GPIO1 + powerup_pin: GPIO1 + wakeup_pin: GPIO1 + vcom_pin: GPIO1 + + text_sensor: - platform: pipsolar diff --git a/tests/test5.yaml b/tests/test5.yaml index 84d4a5a44e..f165617551 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -1,12 +1,15 @@ esphome: name: test5 - platform: ESP32 - board: nodemcu-32s build_path: build/test5 project: name: esphome.test5_project version: "1.0.0" +esp32: + board: nodemcu-32s + framework: + type: esp-idf + wifi: networks: - ssid: 'MySSID' @@ -151,20 +154,3 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor - -display: - - platform: inkplate6 - id: inkplate_display - greyscale: false - partial_updating: false - update_interval: 60s - - ckv_pin: GPIO1 - sph_pin: GPIO1 - gmod_pin: GPIO1 - gpio0_enable_pin: GPIO1 - oe_pin: GPIO1 - spv_pin: GPIO1 - powerup_pin: GPIO1 - wakeup_pin: GPIO1 - vcom_pin: GPIO1 diff --git a/tests/unit_tests/test_core.py b/tests/unit_tests/test_core.py index 37b4d6db57..9a15bf0b9c 100644 --- a/tests/unit_tests/test_core.py +++ b/tests/unit_tests/test_core.py @@ -531,13 +531,13 @@ class TestEsphomeCore: assert target.address == "4.3.2.1" def test_is_esp32(self, target): - target.esp_platform = "ESP32" + target.data[const.KEY_CORE] = {const.KEY_TARGET_PLATFORM: "esp32"} assert target.is_esp32 is True assert target.is_esp8266 is False def test_is_esp8266(self, target): - target.esp_platform = "ESP8266" + target.data[const.KEY_CORE] = {const.KEY_TARGET_PLATFORM: "esp8266"} assert target.is_esp32 is False assert target.is_esp8266 is True diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index ae7a61e01f..ad234250ce 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -3,7 +3,6 @@ from mock import Mock from esphome import cpp_helpers as ch from esphome import const -from esphome.cpp_generator import MockObj @pytest.mark.asyncio @@ -13,15 +12,6 @@ async def test_gpio_pin_expression__conf_is_none(monkeypatch): assert actual is None -@pytest.mark.asyncio -async def test_gpio_pin_expression__new_pin(monkeypatch): - actual = await ch.gpio_pin_expression( - {const.CONF_NUMBER: 42, const.CONF_MODE: "input", const.CONF_INVERTED: False} - ) - - assert isinstance(actual, MockObj) - - @pytest.mark.asyncio async def test_register_component(monkeypatch): var = Mock(base="foo.bar") diff --git a/tests/unit_tests/test_pins.py b/tests/unit_tests/test_pins.py deleted file mode 100644 index d2ffd5f7cd..0000000000 --- a/tests/unit_tests/test_pins.py +++ /dev/null @@ -1,346 +0,0 @@ -""" -Please Note: - -These tests cover the process of identifying information about pins, they do not -check if the definition of MCUs and pins is correct. - -""" -import logging - -import pytest - -from esphome.config_validation import Invalid -from esphome.core import EsphomeCore -from esphome import boards, pins - - -MOCK_ESP8266_BOARD_ID = "_mock_esp8266" -MOCK_ESP8266_PINS = {"X0": 16, "X1": 5, "X2": 4, "LED": 2} -MOCK_ESP8266_BOARD_ALIAS_ID = "_mock_esp8266_alias" -MOCK_ESP8266_FLASH_SIZE = boards.FLASH_SIZE_2_MB - -MOCK_ESP32_BOARD_ID = "_mock_esp32" -MOCK_ESP32_PINS = {"Y0": 12, "Y1": 8, "Y2": 3, "LED": 9, "A0": 8} -MOCK_ESP32_BOARD_ALIAS_ID = "_mock_esp32_alias" - -UNKNOWN_PLATFORM = "STM32" - - -@pytest.fixture -def mock_mcu(monkeypatch): - """ - Add a mock MCU into the lists as a stable fixture - """ - boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_PINS - boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_FLASH_SIZE - boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_BOARD_ID - boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_FLASH_SIZE - boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] = MOCK_ESP32_PINS - boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] = MOCK_ESP32_BOARD_ID - yield - del boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] - del boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] - del boards.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] - del boards.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] - del boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] - del boards.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] - - -@pytest.fixture -def core(monkeypatch, mock_mcu): - core = EsphomeCore() - monkeypatch.setattr(pins, "CORE", core) - return core - - -@pytest.fixture -def core_esp8266(core): - core.esp_platform = "ESP8266" - core.board = MOCK_ESP8266_BOARD_ID - return core - - -@pytest.fixture -def core_esp32(core): - core.esp_platform = "ESP32" - core.board = MOCK_ESP32_BOARD_ID - return core - - -class Test_lookup_pin: - @pytest.mark.parametrize( - "value, expected", - ( - ("X1", 5), - ("MOSI", 13), - ), - ) - def test_valid_esp8266_pin(self, core_esp8266, value, expected): - actual = pins._lookup_pin(value) - - assert actual == expected - - def test_valid_esp8266_pin_alias(self, core_esp8266): - core_esp8266.board = MOCK_ESP8266_BOARD_ALIAS_ID - - actual = pins._lookup_pin("X2") - - assert actual == 4 - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y1", 8), - ("A0", 8), - ("MOSI", 23), - ), - ) - def test_valid_esp32_pin(self, core_esp32, value, expected): - actual = pins._lookup_pin(value) - - assert actual == expected - - def test_valid_32_pin_alias(self, core_esp32): - core_esp32.board = MOCK_ESP32_BOARD_ALIAS_ID - - actual = pins._lookup_pin("Y2") - - assert actual == 3 - - def test_invalid_pin(self, core_esp8266): - with pytest.raises( - Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266." - ): - pins._lookup_pin("X42") - - def test_unsupported_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins._lookup_pin("TX") - - -class Test_translate_pin: - @pytest.mark.parametrize( - "value, expected", - ( - (2, 2), - ("3", 3), - ("GPIO4", 4), - ("TX", 1), - ("Y0", 12), - ), - ) - def test_valid_values(self, core_esp32, value, expected): - actual = pins._translate_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", ({}, None)) - def test_invalid_values(self, core_esp32, value): - with pytest.raises(Invalid, match="This variable only supports"): - pins._translate_pin(value) - - -class Test_validate_gpio_pin: - def test_esp32_valid(self, core_esp32): - actual = pins.validate_gpio_pin("GPIO22") - - assert actual == 22 - - @pytest.mark.parametrize( - "value, match", - ( - (-1, "ESP32: Invalid pin number: -1"), - (40, "ESP32: Invalid pin number: 40"), - (6, "This pin cannot be used on ESP32s and"), - (7, "This pin cannot be used on ESP32s and"), - (8, "This pin cannot be used on ESP32s and"), - (11, "This pin cannot be used on ESP32s and"), - (20, "The pin GPIO20 is not usable on ESP32s"), - (24, "The pin GPIO24 is not usable on ESP32s"), - (28, "The pin GPIO28 is not usable on ESP32s"), - (29, "The pin GPIO29 is not usable on ESP32s"), - (30, "The pin GPIO30 is not usable on ESP32s"), - (31, "The pin GPIO31 is not usable on ESP32s"), - ), - ) - def test_esp32_invalid_pin(self, core_esp32, value, match): - with pytest.raises(Invalid, match=match): - pins.validate_gpio_pin(value) - - @pytest.mark.parametrize("value", (9, 10)) - def test_esp32_warning(self, core_esp32, caplog, value): - caplog.at_level(logging.WARNING) - pins.validate_gpio_pin(value) - - assert len(caplog.messages) == 1 - assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") - - def test_esp8266_valid(self, core_esp8266): - actual = pins.validate_gpio_pin("GPIO12") - - assert actual == 12 - - @pytest.mark.parametrize( - "value, match", - ( - (-1, "ESP8266: Invalid pin number: -1"), - (18, "ESP8266: Invalid pin number: 18"), - (6, "This pin cannot be used on ESP8266s and"), - (7, "This pin cannot be used on ESP8266s and"), - (8, "This pin cannot be used on ESP8266s and"), - (11, "This pin cannot be used on ESP8266s and"), - ), - ) - def test_esp8266_invalid_pin(self, core_esp8266, value, match): - with pytest.raises(Invalid, match=match): - pins.validate_gpio_pin(value) - - @pytest.mark.parametrize("value", (9, 10)) - def test_esp8266_warning(self, core_esp8266, caplog, value): - caplog.at_level(logging.WARNING) - pins.validate_gpio_pin(value) - - assert len(caplog.messages) == 1 - assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") - - def test_unknown_device(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.validate_gpio_pin("0") - - -class Test_input_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.input_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.input_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (17,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.input_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.input_pin(2) - - -class Test_input_pullup_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.input_pullup_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.input_pullup_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (0,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.input_pullup_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.input_pullup_pin(2) - - -class Test_output_pin: - @pytest.mark.parametrize("value, expected", (("X0", 16),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.output_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - ("Y0", 12), - (17, 17), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.output_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", (17,)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.output_pin(value) - - @pytest.mark.parametrize("value", range(34, 40)) - def test_invalid_esp32_values(self, core_esp32, value): - with pytest.raises(Invalid): - pins.output_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.output_pin(2) - - -class Test_analog_pin: - @pytest.mark.parametrize("value, expected", ((17, 17),)) - def test_valid_esp8266_values(self, core_esp8266, value, expected): - actual = pins.analog_pin(value) - - assert actual == expected - - @pytest.mark.parametrize( - "value, expected", - ( - (32, 32), - (39, 39), - ), - ) - def test_valid_esp32_values(self, core_esp32, value, expected): - actual = pins.analog_pin(value) - - assert actual == expected - - @pytest.mark.parametrize("value", ("X0",)) - def test_invalid_esp8266_values(self, core_esp8266, value): - with pytest.raises(Invalid): - pins.analog_pin(value) - - @pytest.mark.parametrize("value", ("Y0",)) - def test_invalid_esp32_values(self, core_esp32, value): - with pytest.raises(Invalid): - pins.analog_pin(value) - - def test_unknown_platform(self, core): - core.esp_platform = UNKNOWN_PLATFORM - - with pytest.raises(NotImplementedError): - pins.analog_pin(2) diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 56bd5119b5..18e040b0a6 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -2,7 +2,7 @@ import esphome.wizard as wz import pytest -from esphome.boards import ESP8266_BOARD_PINS +from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from mock import MagicMock From e65a7d887fa4b7e0188c677ea14d26a39019ff64 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 12:02:37 +0200 Subject: [PATCH 269/285] Bump aioesphomeapi to 9.1.1 (#2350) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c6d967fc33..e97c79985f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.0 esptool==3.1 click==8.0.1 esphome-dashboard==20210908.0 -aioesphomeapi==9.1.0 +aioesphomeapi==9.1.1 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 250bf3f0541f9f70f11634ed6a70ef2d0d25ded8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 20 Sep 2021 13:14:05 +0200 Subject: [PATCH 270/285] CI cache only restore from direct matches (#2351) --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2c434a9b8..45e2f2735c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,8 +96,6 @@ jobs: with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - restore-keys: | - platformio-${{ matrix.pio_cache_key }}- if: matrix.id == 'test' || matrix.id == 'clang-tidy' - name: Install clang tools From bac58bba4d7edddd8a75bfd7caf054d64499cbd3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 20 Sep 2021 22:13:46 +0200 Subject: [PATCH 271/285] fixes compilation error in rtttl (#2357) Compilation error for millis() and delay() after #2303 --- esphome/components/rtttl/rtttl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 84cc2ce48c..d571c2f287 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -1,4 +1,5 @@ #include "rtttl.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { From 7c884329eb570214aff952a597e8922312b7df4b Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 06:34:56 +0200 Subject: [PATCH 272/285] Fix MDNS not registered (#2359) --- esphome/components/mdns/__init__.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index ec568ae2d5..c0c0865643 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,3 +1,4 @@ +from esphome.const import CONF_ID import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE @@ -5,11 +6,26 @@ from esphome.core import CORE CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +mdns_ns = cg.esphome_ns.namespace("mdns") +MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) + + +def _remove_id_if_disabled(value): + value = value.copy() + if value[CONF_DISABLED]: + value.pop(CONF_ID) + return value + + CONF_DISABLED = "disabled" -CONFIG_SCHEMA = cv.Schema( - { - cv.Optional(CONF_DISABLED, default=False): cv.boolean, - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MDNSComponent), + cv.Optional(CONF_DISABLED, default=False): cv.boolean, + } + ), + _remove_id_if_disabled, ) @@ -23,3 +39,6 @@ async def to_code(config): cg.add_library("ESPmDNS", None) elif CORE.is_esp8266: cg.add_library("ESP8266mDNS", None) + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) From 24f445dade6279bbdcb0fb8123e0285d7494a17e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 06:37:13 +0200 Subject: [PATCH 273/285] Fix src_filter in platformio.ini after src_dir change (#2353) --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0e2649e63a..412c18085b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,9 +41,9 @@ lib_deps = build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = - + - + - +<.temp/all-include.cpp> + +<./> + +<../tests/dummy_main.cpp> + +<../.temp/all-include.cpp> [common:esp8266] extends = common From 71fc61117b8f90ab15418447068ce3302d4e67d2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 21 Sep 2021 06:52:01 +0200 Subject: [PATCH 274/285] Fix duplicate defines and restore alphabetical order (#2352) --- esphome/core/defines.h | 48 ++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ac40921a4a..62468dfbfe 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -12,10 +12,9 @@ // Feature flags #define USE_API +#define USE_API_NOISE +#define USE_API_PLAINTEXT #define USE_BINARY_SENSOR -#ifdef USE_ARDUINO -#define USE_CAPTIVE_PORTAL -#endif #define USE_CLIMATE #define USE_COVER #define USE_DEEP_SLEEP @@ -23,14 +22,6 @@ #define USE_FAN #define USE_GRAPH #define USE_HOMEASSISTANT_TIME - -#ifdef USE_ARDUINO -#define USE_JSON -#define USE_NEXTION_TFT_UPLOAD -#define USE_MQTT -#define USE_CAPTIVE_PORTAL -#endif // USE_ARDUINO - #define USE_LIGHT #define USE_LOGGER #define USE_MDNS @@ -43,39 +34,36 @@ #define USE_STATUS_LED #define USE_SWITCH #define USE_TEXT_SENSOR - #define USE_TIME #define USE_WIFI + +// Arduino-specific feature flags #ifdef USE_ARDUINO +#define USE_CAPTIVE_PORTAL +#define USE_JSON +#define USE_NEXTION_TFT_UPLOAD +#define USE_MQTT #define USE_WIFI_WPA2_EAP #endif +// ESP32-specific feature flags #ifdef USE_ESP32 #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA +#define USE_IMPROV +#define USE_SOCKET_IMPL_BSD_SOCKETS #ifdef USE_ARDUINO #define USE_ETHERNET -#endif // USE_ARDUINO - -#define USE_IMPROV -#define USE_SOCKET_IMPL_BSD_SOCKETS -#endif // USE_ESP32 - -#ifdef USE_ESP8266 -#define USE_ADC_SENSOR_VCC -#define USE_SOCKET_IMPL_LWIP_TCP -#define USE_HTTP_REQUEST_ESP8266_HTTPS +#endif #endif -#define USE_API_PLAINTEXT -#define USE_API_NOISE +// ESP8266-specific feature flags +#ifdef USE_ESP8266 +#define USE_ADC_SENSOR_VCC +#define USE_HTTP_REQUEST_ESP8266_HTTPS +#define USE_SOCKET_IMPL_LWIP_TCP +#endif // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. -#define USE_TIME -#define USE_DEEP_SLEEP -#define ESPHOME_BOARD "dummy_board" -#define USE_MDNS -#define USE_API_NOISE -#define USE_API_PLAINTEXT From 491f8cc61193ae7900f13c9d4ea497f870d62c98 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Tue, 21 Sep 2021 06:47:51 -0500 Subject: [PATCH 275/285] Configurable Flash Write Interval (#2119) Co-authored-by: Alex <33379584+alexyao2015@users.noreply.github.com> Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/esp32/__init__.py | 1 + esphome/components/esp32/preferences.cpp | 72 +++++++++++++++++++--- esphome/components/esp8266/__init__.py | 1 + esphome/components/esp8266/preferences.cpp | 64 +++++++++---------- esphome/components/ota/ota_component.cpp | 5 +- esphome/components/preferences/__init__.py | 24 ++++++++ esphome/components/preferences/syncer.h | 23 +++++++ esphome/components/wifi/wifi_component.cpp | 2 + esphome/core/preferences.h | 8 +++ 10 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 esphome/components/preferences/__init__.py create mode 100644 esphome/components/preferences/syncer.h diff --git a/CODEOWNERS b/CODEOWNERS index 385ec4d892..adf96b7382 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -104,6 +104,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core +esphome/components/preferences/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/rc522/* @glmnet diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index c86087cc25..719b7b5f31 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -34,6 +34,7 @@ from .gpio import esp32_pin_to_code # noqa _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] +AUTO_LOAD = ["preferences"] def set_core_data(config): diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 639e6434d6..96b7e7809e 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -4,30 +4,53 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include +#include +#include namespace esphome { namespace esp32 { static const char *const TAG = "esp32.preferences"; +struct NVSData { + std::string key; + std::vector data; +}; + +static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + class ESP32PreferenceBackend : public ESPPreferenceBackend { public: std::string key; uint32_t nvs_handle; bool save(const uint8_t *data, size_t len) override { - esp_err_t err = nvs_set_blob(nvs_handle, key.c_str(), data, len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); - return false; - } - err = nvs_commit(nvs_handle); - if (err != 0) { - ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key.c_str(), len, esp_err_to_name(err)); - return false; + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + obj.data.assign(data, data + len); + return true; + } } + NVSData save{}; + save.key = key; + save.data.assign(data, data + len); + s_pending_save.emplace_back(save); return true; } bool load(uint8_t *data, size_t len) override { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + size_t actual_len; esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); if (err != 0) { @@ -82,6 +105,37 @@ class ESP32Preferences : public ESPPreferences { return ESPPreferenceObject(pref); } + + bool sync() override { + if (s_pending_save.empty()) + return true; + + ESP_LOGD(TAG, "Saving preferences to flash..."); + // goal try write all pending saves even if one fails + bool any_failed = false; + + // go through vector from back to front (makes erase easier/more efficient) + for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { + const auto &save = s_pending_save[i]; + esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), + esp_err_to_name(err)); + any_failed = true; + continue; + } + s_pending_save.erase(s_pending_save.begin() + i); + } + + // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes + esp_err_t err = nvs_commit(nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); + return false; + } + + return !any_failed; + } }; void setup_preferences() { diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 592de06440..6eb4f67115 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -23,6 +23,7 @@ from .gpio import esp8266_pin_to_code # noqa CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) +AUTO_LOAD = ["preferences"] def set_core_data(config): diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 7a8fdb8289..7c0c264054 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -73,33 +73,7 @@ template uint32_t calculate_crc(It first, It last, uint32_t type) { return crc; } -static bool safe_flash() { - if (!s_flash_dirty) - return true; - - ESP_LOGVV(TAG, "Saving preferences to flash..."); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); - return false; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGV(TAG, "Write ESP8266 flash failed!"); - return false; - } - - s_flash_dirty = false; - return true; -} - -static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { +static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) { for (uint32_t i = 0; i < len; i++) { uint32_t j = offset + i; if (j >= ESP8266_FLASH_STORAGE_SIZE) @@ -110,7 +84,7 @@ static bool safe_to_flash(size_t offset, const uint32_t *data, size_t len) { s_flash_dirty = true; *ptr = v; } - return safe_flash(); + return true; } static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { @@ -123,7 +97,7 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { return true; } -static bool safe_to_rtc(size_t offset, const uint32_t *data, size_t len) { +static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) { for (uint32_t i = 0; i < len; i++) if (!esp_rtc_user_mem_write(offset + i, data[i])) return false; @@ -154,9 +128,9 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); if (in_flash) { - return safe_to_flash(offset, buffer.data(), buffer.size()); + return save_to_flash(offset, buffer.data(), buffer.size()); } else { - return safe_to_rtc(offset, buffer.data(), buffer.size()); + return save_to_rtc(offset, buffer.data(), buffer.size()); } } bool load(uint8_t *data, size_t len) override { @@ -245,6 +219,34 @@ class ESP8266Preferences : public ESPPreferences { return make_preference(length, type, false); #endif } + + bool sync() override { + if (!s_flash_dirty) + return true; + if (s_prevent_write) + return false; + + ESP_LOGD(TAG, "Saving preferences to flash..."); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGV(TAG, "Write ESP8266 flash failed!"); + return false; + } + + s_flash_dirty = false; + return true; + } }; void setup_preferences() { diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 7ee3ed2866..f217bd32d1 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -548,7 +548,10 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ return false; } } -void OTAComponent::write_rtc_(uint32_t val) { this->rtc_.save(&val); } +void OTAComponent::write_rtc_(uint32_t val) { + this->rtc_.save(&val); + global_preferences->sync(); +} uint32_t OTAComponent::read_rtc_() { uint32_t val; if (!this->rtc_.load(&val)) diff --git a/esphome/components/preferences/__init__.py b/esphome/components/preferences/__init__.py new file mode 100644 index 0000000000..4844ad6c02 --- /dev/null +++ b/esphome/components/preferences/__init__.py @@ -0,0 +1,24 @@ +from esphome.const import CONF_ID +import esphome.codegen as cg +import esphome.config_validation as cv + +CODEOWNERS = ["@esphome/core"] + +preferences_ns = cg.esphome_ns.namespace("preferences") +IntervalSyncer = preferences_ns.class_("IntervalSyncer", cg.Component) + +CONF_FLASH_WRITE_INTERVAL = "flash_write_interval" +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(IntervalSyncer), + cv.Optional( + CONF_FLASH_WRITE_INTERVAL, default="60s" + ): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_write_interval(config[CONF_FLASH_WRITE_INTERVAL])) + await cg.register_component(var, config) diff --git a/esphome/components/preferences/syncer.h b/esphome/components/preferences/syncer.h new file mode 100644 index 0000000000..af1fe9ba4a --- /dev/null +++ b/esphome/components/preferences/syncer.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/preferences.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace preferences { + +class IntervalSyncer : public Component { + public: + void set_write_interval(uint32_t write_interval) { write_interval_ = write_interval; } + void setup() override { + set_interval(write_interval_, []() { global_preferences->sync(); }); + } + void on_shutdown() override { global_preferences->sync(); } + float get_setup_priority() const override { return setup_priority::BUS; } + + protected: + uint32_t write_interval_; +}; + +} // namespace preferences +} // namespace esphome diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 6e0ce8c044..47cd0ef9ad 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -232,6 +232,8 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid)); strncpy(save.password, password.c_str(), sizeof(save.password)); this->pref_.save(&save); + // ensure it's written immediately + global_preferences->sync(); WiFiAP sta{}; sta.set_ssid(ssid); diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index ff7911ed3a..b3f4b77f78 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -37,6 +37,14 @@ class ESPPreferences { public: virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; + + /** + * Commit pending writes to flash. + * + * @return true if write is successful. + */ + virtual bool sync() = 0; + #ifndef USE_ESP8266 template::value, bool>::type = true> #else From 92a24d52be5ee60772c10554967a0d011e7ca723 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 21 Sep 2021 17:11:58 +0200 Subject: [PATCH 276/285] Fix OTA password mismatch error. (#2363) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index f217bd32d1..5e07bb64e7 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -325,7 +325,7 @@ void OTAComponent::handle_() { ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 - if (!this->writeall_(buf + 64, 32)) { + if (!this->readall_(buf + 64, 32)) { ESP_LOGW(TAG, "Auth: Reading response failed!"); goto error; } From 637b55bfbfe8d55b372271507f11040189d140a0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 21 Sep 2021 17:12:17 +0200 Subject: [PATCH 277/285] Allow compilation against IDF from repository (#2355) * Fix src_filter in platformio.ini after src_dir change * Add -Wno-nonnull-compare to platformio.ini as well * Create default sdkconfig for static analysis * Add more compiler flags to clang ignore list * Clean-up platformio.ini * Remove unnecessary blank line * Fix accidentally dropped library * Don't gitignore sdkconfig.defaults Co-authored-by: Otto winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .gitignore | 1 + platformio.ini | 99 +++++++++++++++++++++++++--------------------- script/clang-tidy | 4 +- script/helpers.py | 31 ++++++--------- sdkconfig.defaults | 17 ++++++++ 5 files changed, 85 insertions(+), 67 deletions(-) create mode 100644 sdkconfig.defaults diff --git a/.gitignore b/.gitignore index c52909f7c9..57b8478bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ tests/.esphome/ .pio/ sdkconfig.* +!sdkconfig.defaults diff --git a/platformio.ini b/platformio.ini index 412c18085b..1901f175af 100644 --- a/platformio.ini +++ b/platformio.ini @@ -4,7 +4,7 @@ ; It's *not* used during runtime. [platformio] -default_envs = esp8266, esp32 +default_envs = esp8266, esp32, esp32-idf src_dir = esphome include_dir = @@ -26,18 +26,8 @@ build_flags = [common] lib_deps = - ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt - ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base - fastled/FastLED@3.3.2 ; fastled_base - makuna/NeoPixelBus@2.6.7 ; neopixelbus - mikalhart/TinyGPSPlus@1.0.2 ; gps - freekode/TM1651@1.0.1 ; tm1651 - seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 - glmnet/Dsmr@0.5 ; dsmr - rweather/Crypto@0.2.0 ; dsmr - esphome/noise-c@0.1.1 ; api - dudanov/MideaUART@1.1.0 ; midea + esphome/noise-c@0.1.1 ; api + makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = @@ -45,8 +35,32 @@ src_filter = +<../tests/dummy_main.cpp> +<../.temp/all-include.cpp> -[common:esp8266] +[common:arduino] extends = common +lib_deps = + ${common.lib_deps} + ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/ArduinoJson-esphomelib@5.13.3 ; json + esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + fastled/FastLED@3.3.2 ; fastled_base + mikalhart/TinyGPSPlus@1.0.2 ; gps + freekode/TM1651@1.0.1 ; tm1651 + seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301 + glmnet/Dsmr@0.5 ; dsmr + rweather/Crypto@0.2.0 ; dsmr + dudanov/MideaUART@1.1.0 ; midea +build_flags = + ${common.build_flags} + -DUSE_ARDUINO + +[common:idf] +extends = common +build_flags = + ${common.build_flags} + -DUSE_ESP_IDF + +[common:esp8266] +extends = common:arduino ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif8266 @ 3.2.0 platform_packages = @@ -55,16 +69,17 @@ platform_packages = framework = arduino board = nodemcuv2 lib_deps = - ${common.lib_deps} + ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp build_flags = - ${common.build_flags} + ${common:arduino.build_flags} -DUSE_ESP8266 + -DUSE_ESP8266_FRAMEWORK_ARDUINO -[common:esp32] -extends = common +[common:esp32-arduino] +extends = common:arduino ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif32 @ 3.3.2 platform_packages = @@ -73,15 +88,16 @@ platform_packages = framework = arduino board = nodemcu-32s lib_deps = - ${common.lib_deps} + ${common:arduino.lib_deps} Hash ; ota (Arduino built-in) esphome/AsyncTCP-esphome@1.2.2 ; async_tcp build_flags = - ${common.build_flags} + ${common:arduino.build_flags} -DUSE_ESP32 -src_filter = ${common.src_filter} + -DUSE_ESP32_FRAMEWORK_ARDUINO -[common:espidf] +[common:esp32-idf] +extends = common:idf ; when changing this also copy it to esphome-docker-base images platform = platformio/espressif32 @ 3.3.2 platform_packages = @@ -90,57 +106,48 @@ platform_packages = framework = espidf board = nodemcu-32s lib_deps = - esphome/noise-c@0.1.1 ; used by api - espressif/esp32-camera@1.0.0 ; used by esp32_camera - NeoPixelBus@2.6.7 + ${common:idf.lib_deps} + espressif/esp32-camera@1.0.0 ; esp32_camera build_flags = - ${common.build_flags} - ${common:esp32.build_flags} - -DUSE_ESP_IDF + ${common:idf.build_flags} + -Wno-nonnull-compare + -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF -src_filter = ${common:esp32.src_filter} [env:esp8266] extends = common:esp8266 build_flags = ${common:esp8266.build_flags} ${runtime.build_flags} - -DUSE_ARDUINO - -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp8266-tidy] extends = common:esp8266 build_flags = ${common:esp8266.build_flags} ${clangtidy.build_flags} - -DUSE_ARDUINO - -DUSE_ESP8266_FRAMEWORK_ARDUINO [env:esp32] -extends = common:esp32 +extends = common:esp32-arduino build_flags = - ${common:esp32.build_flags} + ${common:esp32-arduino.build_flags} ${runtime.build_flags} - -DUSE_ARDUINO - -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-tidy] -extends = common:esp32 +extends = common:esp32-arduino build_flags = - ${common:esp32.build_flags} + ${common:esp32-arduino.build_flags} ${clangtidy.build_flags} - -DUSE_ARDUINO - -DUSE_ESP32_FRAMEWORK_ARDUINO [env:esp32-idf] -extends = common:espidf +extends = common:esp32-idf +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf build_flags = - ${common:espidf.build_flags} + ${common:esp32-idf.build_flags} ${runtime.build_flags} - [env:esp32-idf-tidy] -extends = common:espidf +extends = common:esp32-idf +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32-idf-tidy build_flags = - ${common:espidf.build_flags} + ${common:esp32-idf.build_flags} ${clangtidy.build_flags} diff --git a/script/clang-tidy b/script/clang-tidy index 8a1baf8a22..2612a18c1c 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -54,7 +54,9 @@ def clang_options(idedata): # copy compiler flags, except those clang doesn't understand. cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', '-mlongcalls', '-mtext-section-literals')) + if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', + '-mlongcalls', '-mtext-section-literals', + '-mfix-esp32-psram-cache-issue', '-mfix-esp32-psram-cache-strategy=memw')) # defines cmd.extend(f'-D{define}' for define in idedata['defines']) diff --git a/script/helpers.py b/script/helpers.py index 2c40d47e04..430d8a8e7f 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -112,11 +112,6 @@ def git_ls_files(patterns=None): return {s[3].strip(): int(s[0]) for s in lines} -IDF_TIDY_SDKCONFIG = """\ -CONFIG_BT_ENABLED=y -""" - - def load_idedata(environment): platformio_ini = Path(root_path) / "platformio.ini" temp_idedata = Path(temp_folder) / f"idedata-{environment}.json" @@ -126,30 +121,26 @@ def load_idedata(environment): elif platformio_ini.stat().st_mtime >= temp_idedata.stat().st_mtime: changed = True - if environment == "esp32-idf-tidy": - # sdkconfig needs to be written before idedata is run - # but the file is also modified by the build process, so - # store a temp file to keep track of the + if "idf" in environment: + # remove full sdkconfig when the defaults have changed so that it is regenerated + default_sdkconfig = Path(root_path) / "sdkconfig.defaults" + temp_sdkconfig = Path(temp_folder) / f"sdkconfig-{environment}" - sdk_internal = Path(temp_folder) / f"{environment}-internal-sdkconfig" - sdkconfig = Path(root_path) / f"sdkconfig.{environment}" - if ( - changed - or not sdk_internal.is_file() - or sdk_internal.read_text() != IDF_TIDY_SDKCONFIG - ): + if not temp_sdkconfig.is_file(): + changed = True + elif default_sdkconfig.stat().st_mtime >= temp_sdkconfig.stat().st_mtime: + temp_sdkconfig.unlink() changed = True - sdkconfig.write_text(IDF_TIDY_SDKCONFIG) - sdk_internal.parent.mkdir(exist_ok=True) - sdk_internal.write_text(IDF_TIDY_SDKCONFIG) if not changed: return json.loads(temp_idedata.read_text()) + # ensure temp directory exists before running pio, as it writes sdkconfig to it + Path(temp_folder).mkdir(exist_ok=True) + stdout = subprocess.check_output(["pio", "run", "-t", "idedata", "-e", environment]) match = re.search(r'{\s*".*}', stdout.decode("utf-8")) data = json.loads(match.group()) - temp_idedata.parent.mkdir(exist_ok=True) temp_idedata.write_text(json.dumps(data, indent=2) + "\n") return data diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000000..6b2d6f8f2e --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,17 @@ +# ESP-IDF sdkconfig defaults used for development purposes only, not used during runtime. Used when PlatformIO is ran +# directly from the source directory, e.g. by IDEs or for static analysis (clang-tidy). This should enable all flags +# that are set by any component. + +# esp32 +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=n +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_PARTITION_TABLE_CUSTOM=y +#CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_SINGLE_APP=n + +# esp32_ble +CONFIG_BT_ENABLED=y + +# esp32_camera +CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC=y +CONFIG_ESP32_SPIRAM_SUPPORT=y From bbac1534a3b2b1c961c13e8da23dd4ffdbe2ed22 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 21 Sep 2021 21:59:11 +0200 Subject: [PATCH 278/285] Fix ESP8266 preferences not set up (#2362) --- esphome/components/esp8266/__init__.py | 7 +++++-- esphome/components/esp8266/core.cpp | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 6eb4f67115..93a461ba1f 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -10,11 +10,11 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, ) -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv import esphome.codegen as cg -from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266 +from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS # force import gpio to register pin schema @@ -153,7 +153,10 @@ CONFIG_SCHEMA = cv.All( ) +@coroutine_with_priority(1000) async def to_code(config): + cg.add(esp8266_ns.setup_preferences()) + cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_ESP8266") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 453f772e09..b78600e7a3 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -32,6 +32,28 @@ uint32_t arch_get_cpu_cycle_count() { } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } +void force_link_symbols() { + // Tasmota uses magic bytes in the binary to check if an OTA firmware is compatible + // with their settings - ESPHome uses a different settings system (that can also survive + // erases). So set magic bytes indicating all tasmota versions are supported. + // This only adds 12 bytes of binary size, which is an acceptable price to pay for easier support + // for Tasmota. + // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/settings.ino#L346-L380 + // https://github.com/arendst/Tasmota/blob/b05301b1497942167a015a6113b7f424e42942cd/tasmota/i18n.h#L652-L654 + const static uint32_t TASMOTA_MAGIC_BYTES[] PROGMEM = {0x5AA55AA5, 0xFFFFFFFF, 0xA55AA55A}; + // Force link symbol by using a volatile integer (GCC attribute used does not work because of LTO) + volatile int x = 0; + x = TASMOTA_MAGIC_BYTES[x]; +} + +extern "C" void resetPins() { // NOLINT + // Added in framework 2.7.0 + // usually this sets up all pins to be in INPUT mode + // however, not strictly needed as we set up the pins properly + // ourselves and this causes pins to toggle during reboot. + force_link_symbols(); +} + } // namespace esphome #endif // USE_ESP8266 From c8a8acd46ef0d04cee2af02dde0e87f263860189 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 13:55:49 +1200 Subject: [PATCH 279/285] Fix ESP8266 preference loading (#2367) --- esphome/components/esp8266/preferences.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 7c0c264054..041736943b 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -149,7 +149,12 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { return false; uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); - return buffer[buffer.size() - 1] == crc; + if (buffer[buffer.size() - 1] != crc) { + return false; + } + + memcpy(data, buffer.data(), len); + return true; } }; From c51352d04d5440f507cc3b79193bbb98cbfa17c1 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 13:59:21 +1200 Subject: [PATCH 280/285] Allow non-addressable lights in light partitions (#2256) --- .../light/addressable_light_wrapper.h | 56 +++++++++++++++++ esphome/components/partition/light.py | 60 ++++++++++++++----- esphome/const.py | 2 + tests/test1.yaml | 1 + 4 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 esphome/components/light/addressable_light_wrapper.h diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h new file mode 100644 index 0000000000..813dd43313 --- /dev/null +++ b/esphome/components/light/addressable_light_wrapper.h @@ -0,0 +1,56 @@ +#pragma once + +#include "esphome/core/component.h" +#include "addressable_light.h" + +namespace esphome { +namespace light { + +class AddressableLightWrapper : public light::AddressableLight { + public: + explicit AddressableLightWrapper(light::LightState *light_state) : light_state_(light_state) { + this->wrapper_state_ = new uint8_t[5]; + } + + int32_t size() const override { return 1; } + + void clear_effect_data() override { this->wrapper_state_[4] = 0; } + + light::LightTraits get_traits() override { return this->light_state_->get_traits(); } + + void write_state(light::LightState *state) override { + float gamma = this->light_state_->get_gamma_correct(); + float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma); + float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma); + float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma); + float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma); + float brightness = fmaxf(r, fmaxf(g, b)); + + auto call = this->light_state_->make_call(); + call.set_state(true); + call.set_brightness_if_supported(1.0f); + call.set_color_brightness_if_supported(brightness); + call.set_red_if_supported(r); + call.set_green_if_supported(g); + call.set_blue_if_supported(b); + call.set_white_if_supported(w); + call.set_transition_length_if_supported(0); + call.set_publish(false); + call.set_save(false); + call.perform(); + + this->mark_shown_(); + } + + protected: + light::ESPColorView get_view_internal(int32_t index) const override { + return {&this->wrapper_state_[0], &this->wrapper_state_[1], &this->wrapper_state_[2], + &this->wrapper_state_[3], &this->wrapper_state_[4], &this->correction_}; + } + + light::LightState *light_state_; + uint8_t *wrapper_state_; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 06bee2143e..ada83a123e 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -2,9 +2,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light from esphome.const import ( + CONF_ADDRESSABLE_LIGHT_ID, CONF_FROM, CONF_ID, + CONF_LIGHT_ID, CONF_SEGMENTS, + CONF_SINGLE_LIGHT_ID, CONF_TO, CONF_OUTPUT_ID, CONF_REVERSED, @@ -12,30 +15,47 @@ from esphome.const import ( partitions_ns = cg.esphome_ns.namespace("partition") AddressableSegment = partitions_ns.class_("AddressableSegment") +AddressableLightWrapper = cg.esphome_ns.namespace("light").class_( + "AddressableLightWrapper" +) PartitionLightOutput = partitions_ns.class_( "PartitionLightOutput", light.AddressableLight ) def validate_from_to(value): - if value[CONF_FROM] > value[CONF_TO]: + if CONF_ID in value and value[CONF_FROM] > value[CONF_TO]: raise cv.Invalid( f"From ({value[CONF_FROM]}) must not be larger than to ({value[CONF_TO]})" ) return value +ADDRESSABLE_SEGMENT_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), + cv.Required(CONF_FROM): cv.positive_int, + cv.Required(CONF_TO): cv.positive_int, + cv.Optional(CONF_REVERSED, default=False): cv.boolean, + } +) + +NONADDRESSABLE_SEGMENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( + { + cv.Required(CONF_SINGLE_LIGHT_ID): cv.use_id(light.LightState), + cv.GenerateID(CONF_ADDRESSABLE_LIGHT_ID): cv.declare_id( + AddressableLightWrapper + ), + cv.GenerateID(CONF_LIGHT_ID): cv.declare_id(light.types.LightState), + } +) + CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(PartitionLightOutput), cv.Required(CONF_SEGMENTS): cv.All( cv.ensure_list( - { - cv.Required(CONF_ID): cv.use_id(light.AddressableLightState), - cv.Required(CONF_FROM): cv.positive_int, - cv.Required(CONF_TO): cv.positive_int, - cv.Optional(CONF_REVERSED, default=False): cv.boolean, - }, + cv.Any(ADDRESSABLE_SEGMENT_SCHEMA, NONADDRESSABLE_SEGMENT_SCHEMA), validate_from_to, ), cv.Length(min=1), @@ -47,15 +67,25 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( async def to_code(config): segments = [] for conf in config[CONF_SEGMENTS]: - var = await cg.get_variable(conf[CONF_ID]) - segments.append( - AddressableSegment( - var, - conf[CONF_FROM], - conf[CONF_TO] - conf[CONF_FROM] + 1, - conf[CONF_REVERSED], + if CONF_SINGLE_LIGHT_ID in conf: + wrapper = cg.new_Pvariable( + conf[CONF_ADDRESSABLE_LIGHT_ID], + await cg.get_variable(conf[CONF_SINGLE_LIGHT_ID]), + ) + light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) + await cg.register_component(light_state, conf) + cg.add(cg.App.register_light(light_state)) + segments.append(AddressableSegment(light_state, 0, 1, False)) + + else: + segments.append( + AddressableSegment( + await cg.get_variable(conf[CONF_ID]), + conf[CONF_FROM], + conf[CONF_TO] - conf[CONF_FROM] + 1, + conf[CONF_REVERSED], + ) ) - ) var = cg.new_Pvariable(config[CONF_OUTPUT_ID], segments) await cg.register_component(var, config) diff --git a/esphome/const.py b/esphome/const.py index 6a3296bcea..f032cf0fc3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -320,6 +320,7 @@ CONF_LEVEL = "level" CONF_LG = "lg" CONF_LIBRARIES = "libraries" CONF_LIGHT = "light" +CONF_LIGHT_ID = "light_id" CONF_LIGHTNING_ENERGY = "lightning_energy" CONF_LIGHTNING_THRESHOLD = "lightning_threshold" CONF_LINE_THICKNESS = "line_thickness" @@ -586,6 +587,7 @@ CONF_SHOW_VALUES = "show_values" CONF_SHUNT_RESISTANCE = "shunt_resistance" CONF_SHUNT_VOLTAGE = "shunt_voltage" CONF_SHUTDOWN_MESSAGE = "shutdown_message" +CONF_SINGLE_LIGHT_ID = "single_light_id" CONF_SIZE = "size" CONF_SLEEP_DURATION = "sleep_duration" CONF_SLEEP_PIN = "sleep_pin" diff --git a/tests/test1.yaml b/tests/test1.yaml index ab089dbc15..c1ddf26488 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1615,6 +1615,7 @@ light: - id: addr2 from: 20 to: 25 + - single_light_id: ${roomname}_lights remote_transmitter: - pin: 32 From 40e0100c1efb10be745523c13b66a17f3f02e18f Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Wed, 22 Sep 2021 14:57:16 +1000 Subject: [PATCH 281/285] add = to default font glpyh list (#2361) --- esphome/components/font/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index e47a6f38af..7225bf5bb9 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -72,7 +72,7 @@ def validate_truetype_file(value): DEFAULT_GLYPHS = ( - ' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' + ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_DATA_ID = "raw_data_id" CONF_RAW_GLYPH_ID = "raw_glyph_id" From 11daabc9c25a7f4dda4bf3c5a111ab31e15774cf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 22 Sep 2021 10:32:39 +0200 Subject: [PATCH 282/285] Fix docker pio settings not applied (#2370) --- docker/docker_entrypoint.sh | 16 +++++++++++----- docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh | 4 ++-- docker/hassio-rootfs/etc/services.d/esphome/run | 8 +++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh index b3905d1fed..75d5e0b7b5 100755 --- a/docker/docker_entrypoint.sh +++ b/docker/docker_entrypoint.sh @@ -4,15 +4,21 @@ # otherwise use path in /config (so that PIO packages aren't downloaded on each compile) if [[ -d /cache ]]; then - export PLATFORMIO_CORE_DIR=/cache/platformio + pio_cache_base=/cache/platformio else - export PLATFORMIO_CORE_DIR=/config/.esphome/platformio + pio_cache_base=/config/.esphome/platformio fi -if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then - echo "Creating cache directory ${PLATFORMIO_CORE_DIR}" +if [[ ! -d "${pio_cache_base}" ]]; then + echo "Creating cache directory ${pio_cache_base}" echo "You can change this behavior by mounting a directory to the container's /cache directory." - mkdir -p "${PLATFORMIO_CORE_DIR}" + mkdir -p "${pio_cache_base}" fi +# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json` +# setting `core_dir` would therefore prevent pio from accessing +export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" +export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" +export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" + exec esphome "$@" diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh index 301fe4db63..1073a2fa45 100644 --- a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh +++ b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh @@ -4,6 +4,6 @@ # This files creates all directories used by esphome # ============================================================================== -PLATFORMIO_CORE_DIR=/data/cache/platformio +pio_cache_base=/data/cache/platformio -mkdir -p "${PLATFORMIO_CORE_DIR}" +mkdir -p "${pio_cache_base}" diff --git a/docker/hassio-rootfs/etc/services.d/esphome/run b/docker/hassio-rootfs/etc/services.d/esphome/run index 6218b200bd..a0f20d63d6 100755 --- a/docker/hassio-rootfs/etc/services.d/esphome/run +++ b/docker/hassio-rootfs/etc/services.d/esphome/run @@ -22,7 +22,13 @@ if bashio::config.has_value 'relative_url'; then export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') fi -export PLATFORMIO_CORE_DIR=/data/cache/platformio +pio_cache_base=/data/cache/platformio +# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json` +# setting `core_dir` would therefore prevent pio from accessing +export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" +export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" +export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" + export PLATFORMIO_GLOBALLIB_DIR=/piolibs bashio::log.info "Starting ESPHome dashboard..." From 888e315553dafef858996fa937db51a932fe7e84 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 22 Sep 2021 10:37:46 +0200 Subject: [PATCH 283/285] Fix OTA crash during reading of new bin file. (#2366) Co-authored-by: Maurice Makaay --- esphome/components/ota/ota_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 5e07bb64e7..0c13efa135 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -388,8 +388,10 @@ void OTAComponent::handle_() { size_t requested = std::min(sizeof(buf), ota_size - total); ssize_t read = this->client_->read(buf, requested); if (read == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) + if (errno == EAGAIN || errno == EWOULDBLOCK) { + delay(1); continue; + } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; } From 13b3412b459f6e2e124e22d1806386db0c063a9a Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Wed, 22 Sep 2021 21:12:42 +1200 Subject: [PATCH 284/285] Fix Dallas parent not being set (#2369) --- esphome/components/dallas/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 05c2c8b90c..14ad0efa7b 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -46,5 +46,7 @@ async def to_code(config): if CONF_RESOLUTION in config: cg.add(var.set_resolution(config[CONF_RESOLUTION])) + cg.add(var.set_parent(hub)) + cg.add(hub.register_sensor(var)) await sensor.register_sensor(var, config) From 0929a0f8aa55954bf9269ee2f2dc591605a7da96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Maggioni?= Date: Wed, 22 Sep 2021 11:15:51 +0200 Subject: [PATCH 285/285] Discard senseair commands echoes & fix calibration result check (#2358) --- esphome/components/senseair/senseair.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 8fbb6f69db..610892dd9e 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -78,9 +78,12 @@ uint16_t SenseAirComponent::senseair_checksum_(uint8_t *ptr, uint8_t length) { } void SenseAirComponent::background_calibration() { + // Responses are just echoes but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Starting background calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, nullptr, 0); - this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER) / sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, response, command_length); + this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, response, command_length); } void SenseAirComponent::background_calibration_result() { @@ -98,18 +101,25 @@ void SenseAirComponent::background_calibration_result() { return; } - ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x)", response[2] == 2 ? "OK" : "NOT_OK", response[2], response[3], - response[4]); + // Check if 5th bit (register CI6) is set + ESP_LOGD(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)", (response[4] & 0b100000) != 0 ? "OK" : "NOT_OK", + response[0], response[1], response[2], response[3], response[4], response[5], response[6]); } void SenseAirComponent::abc_enable() { + // Response is just an echo but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_ENABLE) / sizeof(SENSEAIR_COMMAND_ABC_ENABLE[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, response, command_length); } void SenseAirComponent::abc_disable() { + // Response is just an echo but must be read to clear the buffer ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration"); - this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, nullptr, 0); + uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_DISABLE) / sizeof(SENSEAIR_COMMAND_ABC_DISABLE[0]); + uint8_t response[command_length]; + this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, response, command_length); } void SenseAirComponent::abc_get_period() {